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