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 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
117fn 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 #[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 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 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 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 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 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 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 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 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 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
1029fn 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
1082fn 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}