Skip to main content

anodized_core/instrument/
traits.rs

1#[cfg(test)]
2#[path = "traits_tests.rs"]
3mod traits_tests;
4
5use quote::quote;
6use syn::{
7    Attribute, Block, FnArg, ImplItem, ImplItemFn, Pat, ReturnType, TraitItem, TraitItemFn,
8    Visibility, parse_quote,
9};
10
11use crate::{
12    DataSpec, Spec,
13    instrument::{Mode, find_spec_attr, make_item_error},
14};
15
16impl Mode {
17    /// Expand trait items by mangling each method and adding a wrapper default impl.
18    ///
19    /// Mangling a function involves the following:
20    /// 1. Rename the function following the pattern: `fn add` -> `fn __anodized_add`.
21    /// 2. Make a new function with the original name that has a default impl; the
22    ///    default impl performs runtime validation and calls the mangled function.
23    pub fn instrument_trait(
24        &self,
25        spec: DataSpec,
26        mut the_trait: syn::ItemTrait,
27    ) -> syn::Result<syn::ItemTrait> {
28        // Currently we don't support any spec fields for traits themselves.
29        if !spec.is_empty() {
30            return Err(spec.spec_err(
31                "Unsupported spec element on trait. Try placing it on an item inside the trait",
32            ));
33        }
34        let _ = move || spec;
35
36        let mut new_trait_items = Vec::with_capacity(the_trait.items.len() * 5);
37
38        for item in the_trait.items.into_iter() {
39            match item {
40                TraitItem::Fn(mut func) => {
41                    let (spec_attr, other_attrs) = find_spec_attr(func.attrs)?;
42                    func.attrs = other_attrs;
43                    // NOTE: We have no way of knowing which attributes are
44                    //   "external" - meant for the interface and belong on the wrapper,
45                    //   "internal" - meant for the mangled implementation.
46                    //   Right now we put all attribs on both functions, but that's certainly
47                    //   not going to work in every situation.
48
49                    let fn_spec: Spec = match spec_attr {
50                        Some(spec_attr) => spec_attr.parse_args()?,
51                        None => Spec::empty(),
52                    };
53
54                    let attrs: [Attribute; 2] = [
55                        parse_quote!(#[doc(hidden)]),
56                        parse_quote!(#[allow(warnings)]),
57                    ];
58
59                    if let Self::EmbedSpecs = self {
60                        // Embed `spec` elements as `__anodized_fn_*` items.
61                        let spec_requires_fn = TraitItemFn {
62                            attrs: attrs.to_vec(),
63                            sig: Self::build_precondition_fn_sig(
64                                "__anodized_fn_requires",
65                                &func.sig,
66                            ),
67                            default: Some(Self::build_precondition_fn_body(
68                                &fn_spec.requires,
69                                &fn_spec.maintains,
70                            )),
71                            semi_token: None,
72                        };
73                        let spec_ensures_fn = TraitItemFn {
74                            attrs: attrs.to_vec(),
75                            sig: Self::build_postcondition_fn_sig(
76                                "__anodized_fn_ensures",
77                                &func.sig,
78                            ),
79                            default: Some(Self::build_postcondition_fn_body(
80                                &fn_spec.maintains,
81                                &fn_spec.captures,
82                                &fn_spec.ensures,
83                            )?),
84                            semi_token: None,
85                        };
86
87                        new_trait_items.push(TraitItem::Fn(spec_requires_fn));
88                        new_trait_items.push(TraitItem::Fn(spec_ensures_fn));
89                    }
90
91                    if self.changes_anything() {
92                        let spec_trait_qualifiers_const = Self::build_qualifier_const_item(
93                            &attrs,
94                            "__anodized_fn_qualifiers_trait",
95                            fn_spec.qualifiers,
96                            &func.sig.ident,
97                        );
98                        let spec_qualifiers_const = Self::build_qualifier_const_item(
99                            &attrs,
100                            "__anodized_fn_qualifiers",
101                            fn_spec.qualifiers,
102                            &func.sig.ident,
103                        );
104                        new_trait_items.push(TraitItem::Const(spec_trait_qualifiers_const));
105                        new_trait_items.push(TraitItem::Const(spec_qualifiers_const));
106                    }
107
108                    if let Some(default_body) = &mut func.default {
109                        // Handle loop specs in the body of the default impl.
110                        self.instrument_loops_in_fn_body(default_body)?;
111                    }
112
113                    if let Mode::InjectChecks(_) = self {
114                        let mangled_ident = mangle_ident(&func.sig.ident);
115
116                        let mut mangled_fn = func.clone();
117                        mangled_fn.sig.ident = mangled_ident.clone();
118                        mangled_fn.attrs.retain(|attr| !attr.path().is_ident("doc"));
119                        mangled_fn.attrs.push(parse_quote!(#[doc(hidden)]));
120                        new_trait_items.push(TraitItem::Fn(mangled_fn));
121
122                        let call_args = build_call_args(&func.sig.inputs)?;
123                        let mut forwarding_body: Block = parse_quote!({
124                            Self::#mangled_ident(#(#call_args),*)
125                        });
126
127                        self.instrument_fn(&fn_spec, &func.sig, &mut forwarding_body)?;
128
129                        func.default = Some(forwarding_body);
130                        func.semi_token = None;
131                    }
132
133                    if let Self::InjectChecks(check_settings) = self
134                        && let Some(ref panic_settings) = check_settings.does_panic
135                        && panic_settings.has_try_fn
136                    {
137                        // Build a wrapper that forwards to the "try_fn" entry point.
138                        let mut wrapper_func = func.clone();
139                        let mut wrapper_body: Block = parse_quote!({});
140                        let mangled_ident = Self::build_try_fn_wrapper(
141                            true,
142                            &mut wrapper_func.sig,
143                            &mut wrapper_body,
144                        );
145                        wrapper_func.default = Some(wrapper_body);
146                        new_trait_items.push(TraitItem::Fn(wrapper_func));
147
148                        // Create the "try_fn" entry point for e.g. fuzzing and PBT.
149                        func.sig.ident = mangled_ident;
150                        func.sig.output = match func.sig.output {
151                            ReturnType::Default => {
152                                parse_quote!(-> ::anodized::result::Result<()>)
153                            }
154                            ReturnType::Type(ra, ty) => {
155                                parse_quote!(#ra ::anodized::result::Result<#ty>)
156                            }
157                        };
158                        func.attrs = vec![parse_quote!(#[doc(hidden)]), parse_quote!(#[inline])];
159                    }
160
161                    new_trait_items.push(TraitItem::Fn(func));
162                }
163                TraitItem::Const(mut const_item) => {
164                    let (spec, attrs) = find_spec_attr(const_item.attrs)?;
165                    if let Some(ref spec_attr) = spec {
166                        return Err(make_item_error(&spec_attr, "trait const"));
167                    }
168                    const_item.attrs = attrs;
169                    new_trait_items.push(TraitItem::Const(const_item));
170                }
171                TraitItem::Type(mut type_item) => {
172                    let (spec, attrs) = find_spec_attr(type_item.attrs)?;
173                    if let Some(ref spec_attr) = spec {
174                        return Err(make_item_error(&spec_attr, "trait type"));
175                    }
176                    type_item.attrs = attrs;
177                    new_trait_items.push(TraitItem::Type(type_item));
178                }
179                TraitItem::Macro(mut macro_item) => {
180                    let (spec, attrs) = find_spec_attr(macro_item.attrs)?;
181                    if let Some(ref spec_attr) = spec {
182                        return Err(make_item_error(&spec_attr, "trait macro"));
183                    }
184                    macro_item.attrs = attrs;
185                    new_trait_items.push(TraitItem::Macro(macro_item));
186                }
187                TraitItem::Verbatim(token_stream) => {
188                    new_trait_items.push(TraitItem::Verbatim(token_stream));
189                }
190                _ => unimplemented!(),
191            }
192        }
193        the_trait.items = new_trait_items;
194        Ok(the_trait)
195    }
196
197    /// Expand impl items by mangling methods for trait impls.
198    ///
199    /// The `#[spec]` attribute on an impl `fn` must narrow the `#[spec]` of the trait `fn`:
200    /// - The impl's preconditions must follow from the trait's preconditions.
201    /// - The impl's postconditions must entail the trait's postconditions.
202    pub fn instrument_trait_impl(
203        &self,
204        spec: DataSpec,
205        mut the_impl: syn::ItemImpl,
206    ) -> syn::Result<syn::ItemImpl> {
207        let Some((trait_bang, ref trait_path, _trait_for)) = the_impl.trait_ else {
208            return Err(make_item_error(&the_impl, "inherent impl"));
209        };
210
211        if trait_bang.is_some() {
212            return Err(make_item_error(&the_impl, "negative trait impl"));
213        }
214
215        if !spec.is_empty() {
216            return Err(spec.spec_err("Unsupported spec element on trait impl."));
217        }
218
219        let mut new_items = Vec::with_capacity(the_impl.items.len() * 4);
220
221        for item in the_impl.items.into_iter() {
222            match item {
223                ImplItem::Fn(mut func) => {
224                    let (spec_attr, func_attrs) = find_spec_attr(func.attrs)?;
225                    func.attrs = func_attrs;
226
227                    if func.sig.ident.to_string().starts_with("__anodized_") {
228                        return Err(syn::Error::new_spanned(
229                            func.sig.ident,
230                            r#"An item with the `__anodized_` prefix is internal. Do not implement it directly.
231Instead, ensure that both the trait and the impl fn have a `#[spec]` annotation."#,
232                        ));
233                    }
234
235                    let fn_spec: Spec = match spec_attr {
236                        Some(spec_attr) => spec_attr.parse_args()?,
237                        None => Spec::empty(),
238                    };
239
240                    let attrs: [Attribute; 2] = [
241                        parse_quote!(#[doc(hidden)]),
242                        parse_quote!(#[allow(warnings)]),
243                    ];
244
245                    if let Self::EmbedSpecs = self {
246                        // Embed `spec` elements as `__anodized_fn_*` items.
247                        let spec_requires_fn = ImplItemFn {
248                            attrs: attrs.to_vec(),
249                            sig: Self::build_precondition_fn_sig(
250                                "__anodized_fn_requires",
251                                &func.sig,
252                            ),
253                            block: Self::build_precondition_fn_body(
254                                &fn_spec.requires,
255                                &fn_spec.maintains,
256                            ),
257                            vis: Visibility::Inherited,
258                            defaultness: None,
259                        };
260                        let spec_ensures_fn = ImplItemFn {
261                            attrs: attrs.to_vec(),
262                            sig: Self::build_postcondition_fn_sig(
263                                "__anodized_fn_ensures",
264                                &func.sig,
265                            ),
266                            block: Self::build_postcondition_fn_body(
267                                &fn_spec.maintains,
268                                &fn_spec.captures,
269                                &fn_spec.ensures,
270                            )?,
271                            vis: Visibility::Inherited,
272                            defaultness: None,
273                        };
274
275                        new_items.push(ImplItem::Fn(spec_requires_fn));
276                        new_items.push(ImplItem::Fn(spec_ensures_fn));
277                    }
278
279                    if self.changes_anything() {
280                        let spec_qualifiers_const = Self::build_qualifier_const_item(
281                            &attrs,
282                            "__anodized_fn_qualifiers",
283                            fn_spec.qualifiers,
284                            &func.sig.ident,
285                        );
286                        new_items.push(ImplItem::Const(spec_qualifiers_const));
287                    }
288
289                    if let Mode::InjectChecks(_) = self {
290                        self.with_try_fn(false).instrument_fn(
291                            &fn_spec,
292                            &func.sig,
293                            &mut func.block,
294                        )?;
295
296                        // Add a compile-time check to the body.
297                        func.block.stmts.insert(
298                            0,
299                            Self::build_qualifier_check_stmt(
300                                &func.sig.ident,
301                                &the_impl.self_ty,
302                                trait_path,
303                            ),
304                        );
305
306                        func.sig.ident = mangle_ident(&func.sig.ident);
307
308                        // Add a default `#[inline]` attribute unless one is already there.
309                        // The caller can supress this with `#[inline(never)]`
310                        if !has_inline_attr(&func.attrs) {
311                            func.attrs.push(parse_quote!(#[inline]));
312                        }
313                    }
314
315                    new_items.push(ImplItem::Fn(func));
316                }
317                ImplItem::Const(mut const_item) => {
318                    let (spec, attrs) = find_spec_attr(const_item.attrs)?;
319                    if let Some(ref spec_attr) = spec {
320                        return Err(make_item_error(&spec_attr, "trait impl const"));
321                    }
322                    const_item.attrs = attrs;
323                    new_items.push(ImplItem::Const(const_item));
324                }
325                ImplItem::Type(mut type_item) => {
326                    let (spec, attrs) = find_spec_attr(type_item.attrs)?;
327                    if let Some(ref spec_attr) = spec {
328                        return Err(make_item_error(&spec_attr, "trait impl type"));
329                    }
330                    type_item.attrs = attrs;
331                    new_items.push(ImplItem::Type(type_item));
332                }
333                ImplItem::Macro(mut macro_item) => {
334                    let (spec, attrs) = find_spec_attr(macro_item.attrs)?;
335                    if let Some(ref spec_attr) = spec {
336                        return Err(make_item_error(&spec_attr, "trait impl macro"));
337                    }
338                    macro_item.attrs = attrs;
339                    new_items.push(ImplItem::Macro(macro_item));
340                }
341                ImplItem::Verbatim(token_stream) => {
342                    new_items.push(ImplItem::Verbatim(token_stream))
343                }
344                _ => unimplemented!(),
345            };
346        }
347
348        the_impl.items = new_items;
349        Ok(the_impl)
350    }
351}
352
353/// Build argument tokens for calling the mangled trait method from the wrapper.
354///
355/// Purpose: the wrapper method needs to forward its arguments to the mangled
356/// implementation, so this extracts a usable token for each input.
357///
358/// Examples (inputs -> output tokens):
359/// - `fn f(&self, x: i32)` -> `self, x`
360/// - `fn f(self, a: u8, b: u8)` -> `self, a, b`
361///
362/// The caller is responsible for ensuring these tokens are used in a call
363/// expression like `Self::__anodized_f(#(#args),*)`.
364///
365/// Callers: only `instrument_trait` in this module should use this; it is not
366/// part of the public API.
367fn build_call_args(
368    inputs: &syn::punctuated::Punctuated<FnArg, syn::Token![,]>,
369) -> syn::Result<Vec<proc_macro2::TokenStream>> {
370    let mut args = Vec::new();
371    for input in inputs.iter() {
372        match input {
373            FnArg::Receiver(_) => {
374                args.push(quote! { self });
375            }
376            FnArg::Typed(pat) => match pat.pat.as_ref() {
377                Pat::Ident(pat_ident) => {
378                    let ident = &pat_ident.ident;
379                    args.push(quote! { #ident });
380                }
381                _ => {
382                    return Err(syn::Error::new_spanned(
383                        &pat.pat,
384                        "unsupported pattern in trait method arguments",
385                    ));
386                }
387            },
388        }
389    }
390    Ok(args)
391}
392
393/// Prefix an identifier with `__anodized_`, preserving the original span.
394/// Used when generating mangled method names in trait and impl expansion.
395fn mangle_ident(original_ident: &syn::Ident) -> syn::Ident {
396    syn::Ident::new(
397        &format!("__anodized_{original_ident}"),
398        original_ident.span(),
399    )
400}
401
402/// Checks to see if any `#[inline]` (with or without arg) exists in the function's attribs.
403fn has_inline_attr(attrs: &[syn::Attribute]) -> bool {
404    attrs.iter().any(|attr| attr.path().is_ident("inline"))
405}