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