Skip to main content

test_fuzz_macro/
lib.rs

1#![deny(clippy::unwrap_used)]
2
3use darling::{FromMeta, ast::NestedMeta};
4use itertools::MultiUnzip;
5use proc_macro::TokenStream;
6use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
7use quote::{ToTokens, quote};
8use std::{
9    collections::{BTreeMap, BTreeSet},
10    env::var,
11    str::FromStr,
12    sync::{
13        LazyLock,
14        atomic::{AtomicU32, Ordering},
15    },
16};
17use syn::{
18    Attribute, Block, Expr, Field, FieldValue, File, FnArg, GenericArgument, GenericParam,
19    Generics, Ident, ImplItem, ImplItemFn, ItemFn, ItemImpl, ItemMod, LifetimeParam, PatType, Path,
20    PathArguments, PathSegment, Receiver, ReturnType, Signature, Stmt, Type, TypeParam, TypePath,
21    TypeReference, TypeSlice, Visibility, WhereClause, WherePredicate, parse::Parser,
22    parse_macro_input, parse_quote, parse_str, parse2, punctuated::Punctuated, token,
23};
24
25mod ord_type;
26use ord_type::OrdType;
27
28mod pat_utils;
29
30mod type_utils;
31
32type Attrs = Vec<Attribute>;
33
34type Conversions = BTreeMap<OrdType, (Type, bool)>;
35
36static CARGO_CRATE_NAME: LazyLock<String> =
37    LazyLock::new(|| var("CARGO_CRATE_NAME").expect("Could not get `CARGO_CRATE_NAME`"));
38
39#[derive(FromMeta)]
40struct TestFuzzImplOpts {}
41
42#[proc_macro_attribute]
43pub fn test_fuzz_impl(args: TokenStream, item: TokenStream) -> TokenStream {
44    let attr_args =
45        NestedMeta::parse_meta_list(args.into()).expect("Could not parse attribute args");
46    let _ =
47        TestFuzzImplOpts::from_list(&attr_args).expect("Could not parse `test_fuzz_impl` options");
48
49    let item = parse_macro_input!(item as ItemImpl);
50    let ItemImpl {
51        attrs,
52        defaultness,
53        unsafety,
54        impl_token,
55        generics,
56        trait_,
57        self_ty,
58        brace_token: _,
59        items,
60    } = item;
61
62    let (_, _, where_clause) = generics.split_for_impl();
63
64    // smoelius: Without the next line, you get:
65    //   the trait `quote::ToTokens` is not implemented for `(std::option::Option<syn::token::Bang>,
66    // syn::Path, syn::token::For)`
67    let (trait_path, trait_) = trait_.map_or((None, None), |(bang, path, for_)| {
68        (Some(path.clone()), Some(quote! { #bang #path #for_ }))
69    });
70
71    let (impl_items, modules) = map_impl_items(&generics, trait_path.as_ref(), &self_ty, &items);
72    if modules.is_empty() {
73        let span = impl_token.span;
74        let file = span.file();
75        let line = span.start().line;
76        let column = span.start().column + 1;
77        eprintln!(
78            "{file}:{line}:{column}: Warning: No `test_fuzz` attributes found in `impl` block"
79        );
80    }
81
82    let result = quote! {
83        #(#attrs)* #defaultness #unsafety #impl_token #generics #trait_ #self_ty #where_clause {
84            #(#impl_items)*
85        }
86
87        #(#modules)*
88    };
89    log(&result.to_token_stream());
90    result.into()
91}
92
93fn map_impl_items(
94    generics: &Generics,
95    trait_path: Option<&Path>,
96    self_ty: &Type,
97    items: &[ImplItem],
98) -> (Vec<ImplItem>, Vec<ItemMod>) {
99    let impl_items_modules = items
100        .iter()
101        .map(map_impl_item(generics, trait_path, self_ty));
102
103    let (impl_items, modules): (Vec<_>, Vec<_>) = impl_items_modules.unzip();
104
105    let modules = modules.into_iter().flatten().collect();
106
107    (impl_items, modules)
108}
109
110fn map_impl_item<'a>(
111    generics: &'a Generics,
112    trait_path: Option<&'a Path>,
113    self_ty: &'a Type,
114) -> impl Fn(&ImplItem) -> (ImplItem, Option<ItemMod>) + 'a {
115    let generics = generics.clone();
116    let self_ty = self_ty.clone();
117    move |impl_item| {
118        if let ImplItem::Fn(impl_item_fn) = &impl_item {
119            map_impl_item_fn(&generics, trait_path, &self_ty, impl_item_fn)
120        } else {
121            (impl_item.clone(), None)
122        }
123    }
124}
125
126// smoelius: This function is slightly misnamed. The mapped item could actually be an associated
127// function. I am keeping this name to be consistent with `ImplItem::Method`.
128// smoelius: In `syn` 2.0, `ImplItem::Method` was renamed to `ImplItem::Fn`:
129// https://github.com/dtolnay/syn/releases/tag/2.0.0
130fn map_impl_item_fn(
131    generics: &Generics,
132    trait_path: Option<&Path>,
133    self_ty: &Type,
134    impl_item_fn: &ImplItemFn,
135) -> (ImplItem, Option<ItemMod>) {
136    let ImplItemFn {
137        attrs,
138        vis,
139        defaultness,
140        sig,
141        block,
142    } = &impl_item_fn;
143
144    let mut attrs = attrs.clone();
145
146    attrs.iter().position(is_test_fuzz).map_or_else(
147        || (parse_quote!( #impl_item_fn ), None),
148        |i| {
149            let attr = attrs.remove(i);
150            let opts = opts_from_attr(&attr);
151            let (method, module) = map_method_or_fn(
152                &generics.clone(),
153                trait_path,
154                Some(self_ty),
155                &opts,
156                &attrs,
157                vis,
158                defaultness.as_ref(),
159                sig,
160                block,
161            );
162            (parse_quote!( #method ), Some(module))
163        },
164    )
165}
166
167#[allow(clippy::struct_excessive_bools)]
168#[derive(Clone, Debug, Default, FromMeta)]
169struct TestFuzzOpts {
170    #[darling(default)]
171    bounds: Option<String>,
172    #[darling(multiple)]
173    convert: Vec<String>,
174    #[darling(default)]
175    enable_in_production: bool,
176    #[darling(default)]
177    execute_with: Option<String>,
178    #[darling(default)]
179    generic_args: Option<String>,
180    #[darling(default)]
181    impl_generic_args: Option<String>,
182    #[darling(default)]
183    no_auto_generate: bool,
184    #[darling(default)]
185    only_generic_args: bool,
186    #[darling(default)]
187    rename: Option<Ident>,
188}
189
190#[proc_macro_attribute]
191pub fn test_fuzz(args: TokenStream, item: TokenStream) -> TokenStream {
192    let attr_args =
193        NestedMeta::parse_meta_list(args.into()).expect("Could not parse attribute args");
194    let opts = TestFuzzOpts::from_list(&attr_args).expect("Could not parse `test_fuzz` options");
195
196    let item = parse_macro_input!(item as ItemFn);
197    let ItemFn {
198        attrs,
199        vis,
200        sig,
201        block,
202    } = &item;
203    let (item, module) = map_method_or_fn(
204        &Generics::default(),
205        None,
206        None,
207        &opts,
208        attrs,
209        vis,
210        None,
211        sig,
212        block,
213    );
214    let result = quote! {
215        #item
216        #module
217    };
218    log(&result.to_token_stream());
219    result.into()
220}
221
222#[allow(
223    clippy::ptr_arg,
224    clippy::too_many_arguments,
225    clippy::too_many_lines,
226    clippy::trivially_copy_pass_by_ref
227)]
228#[cfg_attr(dylint_lib = "supplementary", allow(commented_out_code))]
229fn map_method_or_fn(
230    generics: &Generics,
231    trait_path: Option<&Path>,
232    self_ty: Option<&Type>,
233    opts: &TestFuzzOpts,
234    attrs: &Vec<Attribute>,
235    vis: &Visibility,
236    defaultness: Option<&token::Default>,
237    sig: &Signature,
238    block: &Block,
239) -> (TokenStream2, ItemMod) {
240    let mut sig = sig.clone();
241    let stmts = &block.stmts;
242
243    let warn_if_function_is_nontrivial = if stmts.len() >= 2 {
244        let span = sig.ident.span();
245        let file = span.file();
246        let line = span.start().line;
247        let column = span.start().column + 1;
248        let ident = sig.ident.to_string();
249        quote! {
250            eprintln!(
251                "{}:{}:{}: Warning: Coverage will not be shown for `{}`. To see coverage for \
252                 `{ident}`, apply `test-fuzz` to a wrapper function that calls `{ident}`.",
253                #file,
254                #line,
255                #column,
256                ident = #ident
257            );
258        }
259    } else {
260        quote! {}
261    };
262
263    let mut conversions = Conversions::new();
264    opts.convert.iter().for_each(|s| {
265        let tokens = TokenStream::from_str(s).expect("Could not tokenize string");
266        let args = Parser::parse(Punctuated::<Type, token::Comma>::parse_terminated, tokens)
267            .expect("Could not parse `convert` argument");
268        assert!(args.len() == 2, "Could not parse `convert` argument");
269        let mut iter = args.into_iter();
270        let key = iter.next().expect("Should have two `convert` arguments");
271        let value = iter.next().expect("Should have two `convert` arguments");
272        conversions.insert(OrdType(key), (value, false));
273    });
274
275    let opts_impl_generic_args = opts
276        .impl_generic_args
277        .as_deref()
278        .map(parse_generic_arguments);
279
280    let opts_generic_args = opts.generic_args.as_deref().map(parse_generic_arguments);
281
282    // smoelius: Error early.
283    #[cfg(fuzzing)]
284    if !opts.only_generic_args {
285        if is_generic(generics) && opts_impl_generic_args.is_none() {
286            panic!(
287                "`{}` appears in a generic impl but `impl_generic_args` was not specified",
288                sig.ident.to_string(),
289            );
290        }
291
292        if is_generic(&sig.generics) && opts_generic_args.is_none() {
293            panic!(
294                "`{}` is generic but `generic_args` was not specified",
295                sig.ident.to_string(),
296            );
297        }
298    }
299
300    let mut attrs = attrs.clone();
301    let maybe_use_cast_checks = if cfg!(feature = "__cast_checks") {
302        attrs.push(parse_quote! {
303            #[test_fuzz::cast_checks::enable]
304        });
305        quote! {
306            use test_fuzz::cast_checks;
307        }
308    } else {
309        quote! {}
310    };
311
312    let impl_ty_idents = type_idents(generics);
313    let ty_idents = type_idents(&sig.generics);
314    let combined_type_idents = [impl_ty_idents.clone(), ty_idents.clone()].concat();
315
316    let impl_ty_names: Vec<Expr> = impl_ty_idents
317        .iter()
318        .map(|ident| parse_quote! { std::any::type_name::< #ident >() })
319        .collect();
320    let ty_names: Vec<Expr> = ty_idents
321        .iter()
322        .map(|ident| parse_quote! { std::any::type_name::< #ident >() })
323        .collect();
324
325    let combined_generics = combine_generics(generics, &sig.generics);
326    let combined_generics_deserializable = restrict_to_deserialize(&combined_generics);
327
328    let (impl_generics, ty_generics, where_clause) = combined_generics.split_for_impl();
329    let (impl_generics_deserializable, _, _) = combined_generics_deserializable.split_for_impl();
330
331    let args_where_clause: Option<WhereClause> = opts.bounds.as_ref().map(|bounds| {
332        let tokens = TokenStream::from_str(bounds).expect("Could not tokenize string");
333        let where_predicates = Parser::parse(
334            Punctuated::<WherePredicate, token::Comma>::parse_terminated,
335            tokens,
336        )
337        .expect("Could not parse type bounds");
338        parse_quote! {
339            where #where_predicates
340        }
341    });
342
343    // smoelius: "Constraints don’t count as 'using' a type parameter," as explained by Daniel Keep
344    // here: https://users.rust-lang.org/t/error-parameter-t-is-never-used-e0392-but-i-use-it/5673
345    // So, for each type parameter `T`, add a `PhantomData<T>` member to `Args` to ensure that `T`
346    // is used. See also: https://github.com/rust-lang/rust/issues/23246
347    let (phantom_idents, phantom_tys): (Vec<_>, Vec<_>) =
348        type_generic_phantom_idents_and_types(&combined_generics)
349            .into_iter()
350            .unzip();
351    let phantoms: Vec<FieldValue> = phantom_idents
352        .iter()
353        .map(|ident| {
354            parse_quote! { #ident: std::marker::PhantomData }
355        })
356        .collect();
357
358    let impl_generic_args = opts_impl_generic_args.as_ref().map(args_as_turbofish);
359    let generic_args = opts_generic_args.as_ref().map(args_as_turbofish);
360    let combined_generic_args_base = combine_options(
361        opts_impl_generic_args.clone(),
362        opts_generic_args,
363        |mut left, right| {
364            left.extend(right);
365            left
366        },
367    );
368    let combined_generic_args = combined_generic_args_base.as_ref().map(args_as_turbofish);
369    // smoelius: The macro generates code like this:
370    //  struct Ret(<Args as HasRetTy>::RetTy);
371    // If `Args` has lifetime parameters, this code won't compile. Insert `'static` for each
372    // parameter that is not filled.
373    let combined_generic_args_with_dummy_lifetimes = {
374        let mut args = combined_generic_args_base.unwrap_or_default();
375        let n_lifetime_params = combined_generics.lifetimes().count();
376        let n_lifetime_args = args
377            .iter()
378            .filter(|arg| matches!(arg, GenericArgument::Lifetime(..)))
379            .count();
380        #[allow(clippy::cast_possible_wrap)]
381        let n_missing_lifetime_args =
382            usize::try_from(n_lifetime_params as isize - n_lifetime_args as isize)
383                .expect("n_lifetime_params < n_lifetime_args");
384        let dummy_lifetime = GenericArgument::Lifetime(parse_quote! { 'static });
385        args.extend(std::iter::repeat_n(dummy_lifetime, n_missing_lifetime_args));
386        args_as_turbofish(&args)
387    };
388
389    let self_ty_base = self_ty.and_then(type_utils::type_base);
390
391    let (mut arg_attrs, mut arg_idents, mut arg_tys, fmt_args, mut ser_args, de_args) = {
392        let mut candidates = BTreeSet::new();
393        let result = map_args(
394            &mut conversions,
395            &mut candidates,
396            trait_path,
397            self_ty,
398            sig.inputs.iter_mut(),
399        );
400        for (from, (to, used)) in conversions {
401            assert!(
402                used,
403                r#"Conversion "{}" -> "{}" does not apply to the following candidates: {:#?}"#,
404                from,
405                OrdType(to),
406                candidates
407            );
408        }
409        result
410    };
411    arg_attrs.extend(phantom_idents.iter().map(|_| Attrs::new()));
412    arg_idents.extend_from_slice(&phantom_idents);
413    arg_tys.extend_from_slice(&phantom_tys);
414    ser_args.extend_from_slice(&phantoms);
415    assert_eq!(arg_attrs.len(), arg_idents.len());
416    assert_eq!(arg_attrs.len(), arg_tys.len());
417    let attr_pub_arg_ident_tys: Vec<Field> = arg_attrs
418        .iter()
419        .zip(arg_idents.iter())
420        .zip(arg_tys.iter())
421        .map(|((attrs, ident), ty)| {
422            parse_quote! {
423                #(#attrs)*
424                pub #ident: #ty
425            }
426        })
427        .collect();
428    let pub_arg_ident_tys: Vec<Field> = arg_idents
429        .iter()
430        .zip(arg_tys.iter())
431        .map(|(ident, ty)| {
432            parse_quote! {
433                pub #ident: #ty
434            }
435        })
436        .collect();
437    let autos: Vec<Expr> = arg_tys
438        .iter()
439        .map(|ty| {
440            parse_quote! {
441                test_fuzz::runtime::auto!( #ty ).collect::<Vec<_>>()
442            }
443        })
444        .collect();
445    let args_from_autos = args_from_autos(&arg_idents, &autos);
446    let ret_ty = match &sig.output {
447        ReturnType::Type(_, ty) => self_ty.as_ref().map_or_else(
448            || *ty.clone(),
449            |self_ty| type_utils::expand_self(trait_path, self_ty, ty),
450        ),
451        ReturnType::Default => parse_quote! { () },
452    };
453
454    let target_ident = &sig.ident;
455    let mod_ident = mod_ident(opts, self_ty_base, target_ident);
456
457    // smoelius: This is a hack. When `only_generic_args` is specified, the user should not have
458    // to also specify trait bounds. But `Args` is used to get the module path at runtime via
459    // `type_name`. So when `only_generic_args` is specified, `Args` gets an empty declaration.
460    let empty_generics = Generics {
461        lt_token: None,
462        params: parse_quote! {},
463        gt_token: None,
464        where_clause: None,
465    };
466    let (_, empty_ty_generics, _) = empty_generics.split_for_impl();
467    let (ty_generics_as_turbofish, struct_args) = if opts.only_generic_args {
468        (
469            empty_ty_generics.as_turbofish(),
470            quote! {
471                pub(super) struct Args;
472            },
473        )
474    } else {
475        (
476            ty_generics.as_turbofish(),
477            quote! {
478                pub(super) struct Args #ty_generics #args_where_clause {
479                    #(#pub_arg_ident_tys),*
480                }
481            },
482        )
483    };
484
485    let write_generic_args = quote! {
486        let impl_generic_args = [
487            #(#impl_ty_names),*
488        ];
489        let generic_args = [
490            #(#ty_names),*
491        ];
492        test_fuzz::runtime::write_impl_generic_args::< #mod_ident :: Args #ty_generics_as_turbofish>(&impl_generic_args);
493        test_fuzz::runtime::write_generic_args::< #mod_ident :: Args #ty_generics_as_turbofish>(&generic_args);
494    };
495    let write_args = if opts.only_generic_args {
496        quote! {}
497    } else {
498        quote! {
499            #mod_ident :: write_args::< #(#combined_type_idents),* >(#mod_ident :: Args {
500                #(#ser_args),*
501            });
502        }
503    };
504    let write_generic_args_and_args = quote! {
505        #[cfg(test)]
506        if !test_fuzz::runtime::test_fuzz_enabled() {
507            #write_generic_args
508            #write_args
509        }
510    };
511    let (in_production_write_generic_args_and_args, mod_attr) = if opts.enable_in_production {
512        (
513            quote! {
514                #[cfg(not(test))]
515                if test_fuzz::runtime::write_enabled() {
516                    #write_generic_args
517                    #write_args
518                }
519            },
520            quote! {},
521        )
522    } else {
523        (
524            quote! {},
525            quote! {
526                #[cfg(test)]
527            },
528        )
529    };
530    let auto_generate = if opts.no_auto_generate {
531        quote! {}
532    } else {
533        quote! {
534            #[test]
535            fn auto_generate() {
536                Args #combined_generic_args :: auto_generate();
537            }
538        }
539    };
540    let input_args = {
541        #[cfg(feature = "__persistent")]
542        quote! {}
543        #[cfg(not(feature = "__persistent"))]
544        quote! {
545            let mut args = UsingReader::<_>::read_args #combined_generic_args (std::io::stdin());
546        }
547    };
548    let output_args = {
549        #[cfg(feature = "__persistent")]
550        quote! {}
551        #[cfg(not(feature = "__persistent"))]
552        quote! {
553            args.as_ref().map(|x| {
554                if test_fuzz::runtime::pretty_print_enabled() {
555                    eprint!("{:#?}", x);
556                } else {
557                    eprint!("{:?}", x);
558                };
559            });
560            eprintln!();
561        }
562    };
563    let args_ret_ty: Type = parse_quote! {
564        <Args #combined_generic_args_with_dummy_lifetimes as HasRetTy>::RetTy
565    };
566    let call: Expr = if let Some(self_ty) = self_ty {
567        let opts_impl_generic_args = opts_impl_generic_args.unwrap_or_default();
568        let map = generic_params_map(generics, &opts_impl_generic_args);
569        let self_ty_with_generic_args =
570            type_utils::type_as_turbofish(&type_utils::map_type_generic_params(&map, self_ty));
571        let qualified_self = if let Some(trait_path) = trait_path {
572            let trait_path_with_generic_args = type_utils::path_as_turbofish(
573                &type_utils::map_path_generic_params(&map, trait_path),
574            );
575            quote! {
576                < #self_ty_with_generic_args as #trait_path_with_generic_args >
577            }
578        } else {
579            self_ty_with_generic_args
580        };
581        parse_quote! {
582            #qualified_self :: #target_ident #generic_args (
583                #(#de_args),*
584            )
585        }
586    } else {
587        parse_quote! {
588            super :: #target_ident #generic_args (
589                #(#de_args),*
590            )
591        }
592    };
593    let call_in_environment = if let Some(s) = &opts.execute_with {
594        let execute_with: Expr = parse_str(s).expect("Could not parse `execute_with` argument");
595        parse_quote! {
596            #execute_with (|| #call)
597        }
598    } else {
599        call
600    };
601    let call_in_environment_with_deserialized_arguments = {
602        #[cfg(feature = "__persistent")]
603        quote! {
604            test_fuzz::afl::fuzz!(|data: &[u8]| {
605                let mut args = UsingReader::<_>::read_args #combined_generic_args (data);
606                let ret: Option< #args_ret_ty > = args.map(|mut args|
607                    #call_in_environment
608                );
609            });
610        }
611        #[cfg(not(feature = "__persistent"))]
612        quote! {
613            let ret: Option< #args_ret_ty > = args.map(|mut args|
614                #call_in_environment
615            );
616        }
617    };
618    let output_ret = {
619        #[cfg(feature = "__persistent")]
620        quote! {
621            // smoelius: Suppress unused variable warning.
622            let _: Option< #args_ret_ty > = None;
623        }
624        #[cfg(not(feature = "__persistent"))]
625        quote! {
626            struct Ret( #args_ret_ty );
627            impl std::fmt::Debug for Ret {
628                fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629                    use test_fuzz::runtime::TryDebugFallback;
630                    let mut debug_tuple = fmt.debug_tuple("Ret");
631                    test_fuzz::runtime::TryDebug(&self.0).apply(&mut |value| {
632                        debug_tuple.field(value);
633                    });
634                    debug_tuple.finish()
635                }
636            }
637            let ret = ret.map(Ret);
638            ret.map(|x| {
639                if test_fuzz::runtime::pretty_print_enabled() {
640                    eprint!("{:#?}", x);
641                } else {
642                    eprint!("{:?}", x);
643                };
644            });
645            eprintln!();
646        }
647    };
648    let mod_items = if opts.only_generic_args {
649        quote! {}
650    } else {
651        quote! {
652            // smoelius: It is tempting to want to put all of these functions under `impl Args`.
653            // But `write_args` and `read args` impose different bounds on their arguments. So
654            // I don't think that idea would work.
655            pub(super) fn write_args #impl_generics (Args { #(#arg_idents),* }: Args #ty_generics_as_turbofish) #where_clause {
656                #[derive(serde::Serialize)]
657                struct Args #ty_generics #args_where_clause {
658                    #(#attr_pub_arg_ident_tys),*
659                }
660                let args = Args {
661                    #(#arg_idents),*
662                };
663                test_fuzz::runtime::write_args(&args);
664            }
665
666            struct UsingReader<R>(R);
667
668            impl<R: std::io::Read> UsingReader<R> {
669                pub fn read_args #impl_generics_deserializable (reader: R) -> Option<Args #ty_generics_as_turbofish> #where_clause {
670                    #[derive(serde::Deserialize)]
671                    struct Args #ty_generics #args_where_clause {
672                        #(#attr_pub_arg_ident_tys),*
673                    }
674                    let args = test_fuzz::runtime::read_args::<Args #ty_generics_as_turbofish, _>(reader);
675                    args.map(|Args { #(#arg_idents),* }| #mod_ident :: Args {
676                        #(#arg_idents),*
677                    })
678                }
679            }
680
681            impl #impl_generics std::fmt::Debug for Args #ty_generics #where_clause {
682                fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683                    use test_fuzz::runtime::TryDebugFallback;
684                    let mut debug_struct = fmt.debug_struct("Args");
685                    #(#fmt_args)*
686                    debug_struct.finish()
687                }
688            }
689
690            // smoelius: Inherent associated types are unstable:
691            // https://github.com/rust-lang/rust/issues/8995
692            trait HasRetTy {
693                type RetTy;
694            }
695
696            impl #impl_generics HasRetTy for Args #ty_generics #where_clause {
697                type RetTy = #ret_ty;
698            }
699        }
700    };
701    // smoelius: The `Args`' implementation and the `auto_generate` test won't compile without
702    // generic args.
703    //   Also, cargo-test-fuzz finds targets by looking for tests that end with `_fuzz__::entry`. So
704    // create such a test regardless. If say `only_generic_args` was specified, then give the
705    // test an empty body.
706    let (generic_args_dependent_mod_items, entry_stmts) = if opts.only_generic_args
707        || (generics.type_params().next().is_some() && impl_generic_args.is_none())
708        || (sig.generics.type_params().next().is_some() && generic_args.is_none())
709    {
710        (quote! {}, quote! {})
711    } else {
712        (
713            quote! {
714                impl #impl_generics Args #ty_generics #where_clause {
715                    // smoelius: `#autos` could refer to type parameters. Expanding it in a method
716                    // definition like this ensures such type parameters resolve.
717                    fn auto_generate() {
718                        if !test_fuzz::runtime::test_fuzz_enabled() {
719                            let autos = ( #(#autos,)* );
720                            for args in #args_from_autos {
721                                write_args(args);
722                            }
723                        }
724                    }
725
726                    fn entry() {
727                        test_fuzz::runtime::warn_if_test_fuzz_not_enabled();
728
729                        // smoelius: Do not set the panic hook when replaying. Leave cargo test's
730                        // panic hook in place.
731                        if test_fuzz::runtime::test_fuzz_enabled() {
732                            if test_fuzz::runtime::coverage_enabled()
733                                || test_fuzz::runtime::display_enabled()
734                                || test_fuzz::runtime::replay_enabled()
735                            {
736                                if test_fuzz::runtime::coverage_enabled() {
737                                    #warn_if_function_is_nontrivial
738                                }
739                                #input_args
740                                if test_fuzz::runtime::display_enabled() {
741                                    #output_args
742                                }
743                                if test_fuzz::runtime::coverage_enabled()
744                                    || test_fuzz::runtime::replay_enabled()
745                                {
746                                    #call_in_environment_with_deserialized_arguments
747                                    if test_fuzz::runtime::replay_enabled() {
748                                        #output_ret
749                                    }
750                                }
751                            } else {
752                                std::panic::set_hook(std::boxed::Box::new(|_| std::process::abort()));
753                                #input_args
754                                #call_in_environment_with_deserialized_arguments
755                                let _ = std::panic::take_hook();
756                            }
757                        }
758                    }
759                }
760
761                #auto_generate
762            },
763            quote! {
764                Args #combined_generic_args :: entry();
765            },
766        )
767    };
768    (
769        parse_quote! {
770            #(#attrs)* #vis #defaultness #sig {
771                #maybe_use_cast_checks
772
773                #write_generic_args_and_args
774
775                #in_production_write_generic_args_and_args
776
777                #(#stmts)*
778            }
779        },
780        parse_quote! {
781            #mod_attr
782            mod #mod_ident {
783                use super::*;
784
785                #struct_args
786
787                #mod_items
788
789                #generic_args_dependent_mod_items
790
791                #[test]
792                fn entry() {
793                    #entry_stmts
794                }
795            }
796        },
797    )
798}
799
800fn generic_params_map<'a, 'b>(
801    generics: &'a Generics,
802    impl_generic_args: &'b Punctuated<GenericArgument, token::Comma>,
803) -> BTreeMap<&'a Ident, &'b GenericArgument> {
804    let n = generics
805        .params
806        .len()
807        .checked_sub(impl_generic_args.len())
808        .unwrap_or_else(|| {
809            panic!(
810                "{:?} is shorter than {:?}",
811                generics.params, impl_generic_args
812            );
813        });
814    generics
815        .params
816        .iter()
817        .skip(n)
818        .zip(impl_generic_args)
819        .filter_map(|(key, value)| {
820            if let GenericParam::Type(TypeParam { ident, .. }) = key {
821                Some((ident, value))
822            } else {
823                None
824            }
825        })
826        .collect()
827}
828
829#[allow(clippy::type_complexity)]
830fn map_args<'a, I>(
831    conversions: &mut Conversions,
832    candidates: &mut BTreeSet<OrdType>,
833    trait_path: Option<&Path>,
834    self_ty: Option<&Type>,
835    inputs: I,
836) -> (
837    Vec<Attrs>,
838    Vec<Ident>,
839    Vec<Type>,
840    Vec<Stmt>,
841    Vec<FieldValue>,
842    Vec<Expr>,
843)
844where
845    I: IntoIterator<Item = &'a mut FnArg>,
846{
847    let (attrs, ident, ty, fmt, ser, de): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = inputs
848        .into_iter()
849        .map(map_arg(conversions, candidates, trait_path, self_ty))
850        .multiunzip();
851
852    (attrs, ident, ty, fmt, ser, de)
853}
854
855fn map_arg<'a>(
856    conversions: &'a mut Conversions,
857    candidates: &'a mut BTreeSet<OrdType>,
858    trait_path: Option<&'a Path>,
859    self_ty: Option<&'a Type>,
860) -> impl FnMut(&mut FnArg) -> (Attrs, Ident, Type, Stmt, FieldValue, Expr) + 'a {
861    move |arg| {
862        let (fn_arg_attrs, ident, expr, ty, fmt) = match arg {
863            FnArg::Receiver(Receiver {
864                attrs,
865                reference,
866                mutability,
867                ..
868            }) => {
869                let ident = anonymous_ident();
870                let expr = parse_quote! { self };
871                let reference = reference
872                    .as_ref()
873                    .map(|(and, lifetime)| quote! { #and #lifetime });
874                let ty = parse_quote! { #reference #mutability #self_ty };
875                let fmt = parse_quote! {
876                    test_fuzz::runtime::TryDebug(&self.#ident).apply(&mut |value| {
877                        debug_struct.field("self", value);
878                    });
879                };
880                (attrs, ident, expr, ty, fmt)
881            }
882            FnArg::Typed(PatType { attrs, pat, ty, .. }) => {
883                let ident = match *pat_utils::pat_idents(pat).as_slice() {
884                    [] => anonymous_ident(),
885                    [ident] => ident.clone(),
886                    _ => panic!("Unexpected pattern: {}", pat.to_token_stream()),
887                };
888                let expr = parse_quote! { #ident };
889                let ty = self_ty.as_ref().map_or_else(
890                    || *ty.clone(),
891                    |self_ty| type_utils::expand_self(trait_path, self_ty, ty),
892                );
893                let name = ident.to_string();
894                let fmt = parse_quote! {
895                    test_fuzz::runtime::TryDebug(&self.#ident).apply(&mut |value| {
896                        debug_struct.field(#name, value);
897                    });
898                };
899                (attrs, ident, expr, ty, fmt)
900            }
901        };
902        let attrs = std::mem::take(fn_arg_attrs);
903        let (ty, ser, de) = if attrs.is_empty() {
904            map_typed_arg(conversions, candidates, &ident, &expr, &ty)
905        } else {
906            (
907                parse_quote! { #ty },
908                parse_quote! { #ident: <#ty as std::clone::Clone>::clone( & #expr ) },
909                parse_quote! { args.#ident },
910            )
911        };
912        (attrs, ident, ty, fmt, ser, de)
913    }
914}
915
916fn map_typed_arg(
917    conversions: &mut Conversions,
918    candidates: &mut BTreeSet<OrdType>,
919    ident: &Ident,
920    expr: &Expr,
921    ty: &Type,
922) -> (Type, FieldValue, Expr) {
923    candidates.insert(OrdType(ty.clone()));
924    if let Some((arg_ty, used)) = conversions.get_mut(&OrdType(ty.clone())) {
925        *used = true;
926        return (
927            parse_quote! { #arg_ty },
928            parse_quote! { #ident: <#arg_ty as test_fuzz::FromRef::<#ty>>::from_ref( & #expr ) },
929            parse_quote! { <_ as test_fuzz::Into::<_>>::into(args.#ident) },
930        );
931    }
932    match &ty {
933        Type::Path(path) => map_path_arg(conversions, candidates, ident, expr, path),
934        Type::Reference(ty) => map_ref_arg(conversions, candidates, ident, expr, ty),
935        _ => (
936            parse_quote! { #ty },
937            parse_quote! { #ident: #expr.clone() },
938            parse_quote! { args.#ident },
939        ),
940    }
941}
942
943fn map_path_arg(
944    _conversions: &mut Conversions,
945    _candidates: &mut BTreeSet<OrdType>,
946    ident: &Ident,
947    expr: &Expr,
948    path: &TypePath,
949) -> (Type, FieldValue, Expr) {
950    (
951        parse_quote! { #path },
952        parse_quote! { #ident: #expr.clone() },
953        parse_quote! { args.#ident },
954    )
955}
956
957fn map_ref_arg(
958    conversions: &mut Conversions,
959    candidates: &mut BTreeSet<OrdType>,
960    ident: &Ident,
961    expr: &Expr,
962    ty: &TypeReference,
963) -> (Type, FieldValue, Expr) {
964    let (maybe_mut, mutability) = if ty.mutability.is_some() {
965        ("mut_", quote! { mut })
966    } else {
967        ("", quote! {})
968    };
969    let ty = &*ty.elem;
970    match ty {
971        Type::Path(path) => {
972            if type_utils::match_type_path(path, &["str"]) == Some(PathArguments::None) {
973                let as_maybe_mut_str = Ident::new(&format!("as_{maybe_mut}str"), Span::call_site());
974                (
975                    parse_quote! { String },
976                    parse_quote! { #ident: #expr.to_owned() },
977                    parse_quote! { args.#ident.#as_maybe_mut_str() },
978                )
979            } else {
980                let expr = parse_quote! { (*#expr) };
981                let (ty, ser, de) = map_path_arg(conversions, candidates, ident, &expr, path);
982                (ty, ser, parse_quote! { & #mutability #de })
983            }
984        }
985        Type::Slice(TypeSlice { elem, .. }) => {
986            let as_maybe_mut_slice = Ident::new(&format!("as_{maybe_mut}slice"), Span::call_site());
987            (
988                parse_quote! { Vec<#elem> },
989                parse_quote! { #ident: #expr.to_vec() },
990                parse_quote! { args.#ident.#as_maybe_mut_slice() },
991            )
992        }
993        _ => {
994            let expr = parse_quote! { (*#expr) };
995            let (ty, ser, de) = map_typed_arg(conversions, candidates, ident, &expr, ty);
996            (ty, ser, parse_quote! { & #mutability #de })
997        }
998    }
999}
1000
1001fn opts_from_attr(attr: &Attribute) -> TestFuzzOpts {
1002    attr.parse_args::<TokenStream2>().map_or_else(
1003        |_| TestFuzzOpts::default(),
1004        |tokens| {
1005            let attr_args =
1006                NestedMeta::parse_meta_list(tokens).expect("Could not parse attribute args");
1007            TestFuzzOpts::from_list(&attr_args).expect("Could not parse `test_fuzz` options")
1008        },
1009    )
1010}
1011
1012fn is_test_fuzz(attr: &Attribute) -> bool {
1013    attr.path()
1014        .segments
1015        .iter()
1016        .all(|PathSegment { ident, .. }| ident == "test_fuzz")
1017}
1018
1019fn parse_generic_arguments(s: &str) -> Punctuated<GenericArgument, token::Comma> {
1020    let tokens = TokenStream::from_str(s).expect("Could not tokenize string");
1021    Parser::parse(
1022        Punctuated::<GenericArgument, token::Comma>::parse_terminated,
1023        tokens,
1024    )
1025    .expect("Could not parse generic arguments")
1026}
1027
1028#[cfg(fuzzing)]
1029fn is_generic(generics: &Generics) -> bool {
1030    generics
1031        .params
1032        .iter()
1033        .filter(|param| !matches!(param, GenericParam::Lifetime(_)))
1034        .next()
1035        .is_some()
1036}
1037
1038fn type_idents(generics: &Generics) -> Vec<Ident> {
1039    generics
1040        .params
1041        .iter()
1042        .filter_map(|param| {
1043            if let GenericParam::Type(ty_param) = param {
1044                Some(ty_param.ident.clone())
1045            } else {
1046                None
1047            }
1048        })
1049        .collect()
1050}
1051
1052fn combine_generics(left: &Generics, right: &Generics) -> Generics {
1053    let mut generics = left.clone();
1054    generics.params.extend(right.params.clone());
1055    generics.where_clause = combine_options(
1056        generics.where_clause,
1057        right.where_clause.clone(),
1058        |mut left, right| {
1059            left.predicates.extend(right.predicates);
1060            left
1061        },
1062    );
1063    generics
1064}
1065
1066// smoelius: Is there a better name for this operation? The closest thing I've found is the `<|>`
1067// operation in Haskell's `Alternative` class (thanks, @incertia):
1068// https://en.wikibooks.org/wiki/Haskell/Alternative_and_MonadPlus
1069// ... (<|>) is a binary function which combines two computations.
1070//                                      ^^^^^^^^
1071
1072fn combine_options<T, F>(x: Option<T>, y: Option<T>, f: F) -> Option<T>
1073where
1074    F: FnOnce(T, T) -> T,
1075{
1076    match (x, y) {
1077        (Some(x), Some(y)) => Some(f(x, y)),
1078        (x, None) => x,
1079        (None, y) => y,
1080    }
1081}
1082
1083fn restrict_to_deserialize(generics: &Generics) -> Generics {
1084    let mut generics = generics.clone();
1085    generics.params.iter_mut().for_each(|param| {
1086        if let GenericParam::Type(ty_param) = param {
1087            ty_param
1088                .bounds
1089                .push(parse_quote! { serde::de::DeserializeOwned });
1090        }
1091    });
1092    generics
1093}
1094
1095fn type_generic_phantom_idents_and_types(generics: &Generics) -> Vec<(Ident, Type)> {
1096    generics
1097        .params
1098        .iter()
1099        .filter_map(|param| match param {
1100            GenericParam::Type(TypeParam { ident, .. }) => Some((
1101                anonymous_ident(),
1102                parse_quote! { std::marker::PhantomData< #ident > },
1103            )),
1104            GenericParam::Lifetime(LifetimeParam { lifetime, .. }) => Some((
1105                anonymous_ident(),
1106                parse_quote! { std::marker::PhantomData< & #lifetime () > },
1107            )),
1108            GenericParam::Const(_) => None,
1109        })
1110        .collect()
1111}
1112
1113fn args_as_turbofish(args: &Punctuated<GenericArgument, token::Comma>) -> TokenStream2 {
1114    quote! {
1115        ::<#args>
1116    }
1117}
1118
1119// smoelius: The current strategy for combining auto-generated values is a kind of "round robin."
1120// The strategy ensures that each auto-generated value gets into at least one `Args` value.
1121// smoelius: One problem with the current approach is that it increments `Args` fields in lockstep.
1122// So for any two fields with the same number of values, if value x appears alongside value y, then
1123// whenever x appears, it appears alongside y (and vice versa).
1124fn args_from_autos(idents: &[Ident], autos: &[Expr]) -> Expr {
1125    assert_eq!(idents.len(), autos.len());
1126    let lens: Vec<Expr> = (0..autos.len())
1127        .map(|i| {
1128            let i = Literal::usize_unsuffixed(i);
1129            parse_quote! {
1130                autos.#i.len()
1131            }
1132        })
1133        .collect();
1134    let args: Vec<FieldValue> = (0..autos.len())
1135        .map(|i| {
1136            let ident = &idents[i];
1137            let i = Literal::usize_unsuffixed(i);
1138            parse_quote! {
1139                #ident: autos.#i[(i + #i) % lens[#i]].clone()
1140            }
1141        })
1142        .collect();
1143    parse_quote! {{
1144        let lens = [ #(#lens),* ];
1145        let max = if lens.iter().copied().min().unwrap_or(1) > 0 {
1146            lens.iter().copied().max().unwrap_or(1)
1147        } else {
1148            0
1149        };
1150        (0..max).map(move |i|
1151            Args { #(#args),* }
1152        )
1153    }}
1154}
1155
1156#[allow(unused_variables)]
1157fn mod_ident(opts: &TestFuzzOpts, self_ty_base: Option<&Ident>, target_ident: &Ident) -> Ident {
1158    let mut s = String::new();
1159    if let Some(name) = &opts.rename {
1160        s.push_str(&name.to_string());
1161    } else {
1162        if let Some(ident) = self_ty_base {
1163            s.push_str(&ident.to_string());
1164            s.push('_');
1165        }
1166        s.push_str(&target_ident.to_string());
1167    }
1168    s.push_str("_fuzz__");
1169    Ident::new(&s, Span::call_site())
1170}
1171
1172static INDEX: AtomicU32 = AtomicU32::new(0);
1173
1174fn anonymous_ident() -> Ident {
1175    let index = INDEX.fetch_add(1, Ordering::SeqCst);
1176    Ident::new(&format!("_{index}"), Span::call_site())
1177}
1178
1179fn log(tokens: &TokenStream2) {
1180    if log_enabled() {
1181        let syntax_tree: File = parse2(tokens.clone()).expect("Could not parse tokens");
1182        let formatted = prettyplease::unparse(&syntax_tree);
1183        print!("{formatted}");
1184    }
1185}
1186
1187fn log_enabled() -> bool {
1188    option_env!("TEST_FUZZ_LOG").map_or(false, |value| value == "1" || value == *CARGO_CRATE_NAME)
1189}