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 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
128fn 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 #[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 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 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 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 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 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 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 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 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 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
1072fn 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
1125fn 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}