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