| // Copyright 2024 Google LLC | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | //! Internal crate for use by [`derive_fuzztest`](../derive_fuzztest/index.html). See the | 
 | //! documentation there for usage information. | 
 |  | 
 | use proc_macro::TokenStream; | 
 | use proc_macro2::TokenStream as TokenStream2; | 
 | use quote::quote; | 
 | use syn::{parse::Nothing, spanned::Spanned, ItemFn, Pat, PatType, Type}; | 
 |  | 
 | /// Define a fuzz test. | 
 | /// | 
 | /// All input parameters of the given function must implement `arbitrary::Arbitrary`. | 
 | /// | 
 | /// This macro derives new items based on the given function. | 
 | /// 1. A `fuzz_target!` is generated that can be used with `cargo fuzz`. | 
 | /// 2. Property tests (`quickcheck` or `proptest`, based on which features are enabled) are | 
 | ///    generated that can be tested using `cargo test`. | 
 | /// | 
 | /// See the crate documentation [`derive_fuzztest`](../derive_fuzztest/index.html) for details. | 
 | #[proc_macro_attribute] | 
 | pub fn fuzztest(attr: TokenStream, item: TokenStream) -> TokenStream { | 
 |     fuzztest_impl(attr.into(), item.into()) | 
 |         .unwrap_or_else(|e| e.into_compile_error()) | 
 |         .into() | 
 | } | 
 |  | 
 | fn fuzztest_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> { | 
 |     syn::parse2::<Nothing>(attr)?; | 
 |     let func = syn::parse2::<ItemFn>(item)?; | 
 |     let fn_def = FunctionDefinition::parse(func)?; | 
 |     let original_fn = &fn_def.func; | 
 |     let fuzz_target = derive_fuzz_target(&fn_def); | 
 |     let proptest_target = proptest::derive_proptest(&fn_def); | 
 |     let quickcheck_target = quickcheck::derive_quickcheck(&fn_def); | 
 |  | 
 |     Ok(quote! { | 
 |         #[allow(unused)] | 
 |         #original_fn | 
 |         #fuzz_target | 
 |         #proptest_target | 
 |         #quickcheck_target | 
 |     }) | 
 | } | 
 |  | 
 | /// Define a fuzz target only without corresponding test. | 
 | /// | 
 | /// All input parameters of the given function must implement `arbitrary::Arbitrary`. | 
 | /// | 
 | /// This macro derives a `fuzz_target!` that can be used with `cargo fuzz`. If you wish to generate | 
 | /// property tests that can be used with `cargo test` as well, use [`fuzztest`][macro@fuzztest]. | 
 | /// | 
 | /// See the crate documentation [`derive_fuzztest`](../derive_fuzztest/index.html) for details. | 
 | #[proc_macro_attribute] | 
 | pub fn fuzz(attr: TokenStream, item: TokenStream) -> TokenStream { | 
 |     fuzz_impl(attr.into(), item.into()) | 
 |         .unwrap_or_else(|e| e.into_compile_error()) | 
 |         .into() | 
 | } | 
 |  | 
 | fn fuzz_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> { | 
 |     syn::parse2::<Nothing>(attr)?; | 
 |     let func = syn::parse2::<ItemFn>(item)?; | 
 |     let fn_def = FunctionDefinition::parse(func)?; | 
 |     let original_fn = &fn_def.func; | 
 |     let fuzz_target = derive_fuzz_target(&fn_def); | 
 |  | 
 |     Ok(quote! { | 
 |         #[allow(unused)] | 
 |         #original_fn | 
 |         #fuzz_target | 
 |     }) | 
 | } | 
 |  | 
 | /// Define a property test. | 
 | /// | 
 | /// This is similar to using `quickcheck!` or `proptest::proptest!` directly. | 
 | /// | 
 | /// All input parameters of the given function must implement `arbitrary::Arbitrary`. | 
 | /// | 
 | /// Unlike [`fuzztest`][macro@fuzztest], this macro does not have to be placed in a `[[bin]]` target | 
 | /// and a single file can contain multiple of these tests. The generated tests can be run with | 
 | /// `cargo test` as usual. | 
 | #[proc_macro_attribute] | 
 | pub fn proptest(attr: TokenStream, item: TokenStream) -> TokenStream { | 
 |     proptest_impl(attr.into(), item.into()) | 
 |         .unwrap_or_else(|e| e.into_compile_error()) | 
 |         .into() | 
 | } | 
 |  | 
 | fn proptest_impl(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> { | 
 |     syn::parse2::<Nothing>(attr)?; | 
 |     let func = syn::parse2::<ItemFn>(item)?; | 
 |     let fn_def = FunctionDefinition::parse(func)?; | 
 |     let original_fn = &fn_def.func; | 
 |     let proptest_target = proptest::derive_proptest(&fn_def); | 
 |  | 
 |     Ok(quote! { | 
 |         #[allow(unused)] | 
 |         #original_fn | 
 |         #proptest_target | 
 |     }) | 
 | } | 
 |  | 
 | fn derive_fuzz_target(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { | 
 |     let FunctionDefinition { func, args, types } = fn_def; | 
 |     let func_ident = &func.sig.ident; | 
 |     quote! { | 
 |         #[automatically_derived] | 
 |         #[cfg(fuzzing)] | 
 |         ::libfuzzer_sys::fuzz_target!(|args: ( #(#types),* )| { | 
 |             let ( #(#args),* ) = args;  // https://github.com/rust-fuzz/libfuzzer/issues/77 | 
 |             #func_ident ( #(#args),* ) | 
 |         }); | 
 |  | 
 |         #[cfg(not(any(fuzzing, rust_analyzer)))] | 
 |         fn main() { | 
 |             ::std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(any(feature = "quickcheck", test))] | 
 | mod quickcheck { | 
 |     use crate::FunctionDefinition; | 
 |     use quote::quote; | 
 |  | 
 |     pub(crate) fn derive_quickcheck(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { | 
 |         let FunctionDefinition { func, args, types } = fn_def; | 
 |         let func_ident = &func.sig.ident; | 
 |         let adapted_types: Vec<_> = types | 
 |             .iter() | 
 |             .map(|ty| quote! { ArbitraryAdapter<#ty> }) | 
 |             .collect(); | 
 |         let arg_pattern: Vec<_> = args | 
 |             .iter() | 
 |             .map(|arg| quote! { ArbitraryAdapter(::core::result::Result::Ok(#arg)) }) | 
 |             .collect(); | 
 |         let test_name = quote::format_ident!("quickcheck_{func_ident}"); | 
 |         quote! { | 
 |             #[automatically_derived] | 
 |             #[test] | 
 |             fn #test_name() { | 
 |                 use ::derive_fuzztest::reexport::quickcheck::TestResult; | 
 |                 use ::derive_fuzztest::arbitrary_bridge::ArbitraryAdapter; | 
 |  | 
 |                 fn inner(args: (#(#adapted_types),*)) -> TestResult { | 
 |                     let (#(#arg_pattern),*) = args else { return TestResult::discard() }; | 
 |                     match ::std::panic::catch_unwind(move || { | 
 |                         #func_ident ( #(#args),* ); | 
 |                     }) { | 
 |                         ::core::result::Result::Ok(()) => TestResult::passed(), | 
 |                         ::core::result::Result::Err(e) => TestResult::error(::std::format!("{e:?}")), | 
 |                     } | 
 |                 } | 
 |  | 
 |                 ::derive_fuzztest::reexport::quickcheck::QuickCheck::new().tests(1024) | 
 |                     .quickcheck(inner as fn(_) -> TestResult); | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(not(any(feature = "quickcheck", test)))] | 
 | mod quickcheck { | 
 |     use crate::FunctionDefinition; | 
 |  | 
 |     pub(crate) fn derive_quickcheck(_fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { | 
 |         proc_macro2::TokenStream::default() | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(any(feature = "proptest", test))] | 
 | mod proptest { | 
 |     use crate::FunctionDefinition; | 
 |     use quote::quote; | 
 |     use syn::{Ident, Signature}; | 
 |  | 
 |     pub(crate) fn derive_proptest(fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { | 
 |         let FunctionDefinition { func, args, types } = fn_def; | 
 |         let func_attrs = &func.attrs; | 
 |         let Signature { | 
 |             constness, | 
 |             asyncness, | 
 |             unsafety, | 
 |             abi, | 
 |             fn_token, | 
 |             ident, | 
 |             generics, | 
 |             paren_token: _, | 
 |             inputs: _, | 
 |             variadic: _, | 
 |             output, | 
 |         } = &func.sig; | 
 |         let proptest_ident = Ident::new(&format!("proptest_{ident}"), ident.span()); | 
 |         quote! { | 
 |             #[automatically_derived] | 
 |             #[cfg(test)] | 
 |             mod #proptest_ident { | 
 |                 use super::*; | 
 |                 use ::derive_fuzztest::reexport::proptest; | 
 |                 use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; | 
 |  | 
 |                 proptest::proptest! { | 
 |                     #![proptest_config(proptest::prelude::ProptestConfig { | 
 |                         cases: 1024, | 
 |                         failure_persistence: Some(Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression"))), | 
 |                         ..Default::default() | 
 |                     })] | 
 |                     #[test] | 
 |                     #(#func_attrs)* | 
 |                     #constness #asyncness #unsafety #abi #fn_token #proptest_ident #generics ( args in arb::<(#(#types),*)>() ) #output { | 
 |                         let (#(#args),*) = args; | 
 |                         #ident ( #(#args),* ); | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(not(any(feature = "proptest", test)))] | 
 | mod proptest { | 
 |     use crate::FunctionDefinition; | 
 |  | 
 |     pub(crate) fn derive_proptest(_fn_def: &FunctionDefinition) -> proc_macro2::TokenStream { | 
 |         proc_macro2::TokenStream::default() | 
 |     } | 
 | } | 
 |  | 
 | /// Representation of a function definition annotated with one of the attribute macros in this | 
 | /// crate. | 
 | struct FunctionDefinition { | 
 |     func: ItemFn, | 
 |     args: Vec<Pat>, | 
 |     types: Vec<Type>, | 
 | } | 
 |  | 
 | impl FunctionDefinition { | 
 |     pub fn parse(func: ItemFn) -> syn::Result<Self> { | 
 |         let (args, types) = func | 
 |             .sig | 
 |             .inputs | 
 |             .clone() | 
 |             .into_iter() | 
 |             .map(|arg| match arg { | 
 |                 syn::FnArg::Receiver(arg_receiver) => Err(syn::Error::new( | 
 |                     arg_receiver.span(), | 
 |                     "Receiver not supported", | 
 |                 )), | 
 |                 syn::FnArg::Typed(PatType { | 
 |                     attrs: _, | 
 |                     pat, | 
 |                     colon_token: _, | 
 |                     ty, | 
 |                 }) => Ok((*pat, *ty)), | 
 |             }) | 
 |             .try_fold((Vec::new(), Vec::new()), |(mut args, mut types), result| { | 
 |                 result.map(|(arg, type_)| { | 
 |                     args.push(arg); | 
 |                     types.push(type_); | 
 |                     (args, types) | 
 |                 }) | 
 |             })?; | 
 |         Ok(Self { func, args, types }) | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(test)] | 
 | mod tests { | 
 |     use crate::{fuzz_impl, fuzztest_impl, proptest_impl}; | 
 |     use quote::quote; | 
 |     use syn::parse_quote; | 
 |  | 
 |     /// Assert that a token stream for a `syn::File` is the same as expected. | 
 |     /// | 
 |     /// Usage is similar to `assert_eq!`: | 
 |     /// ```no_run | 
 |     /// assert_syn_file!( | 
 |     ///     macro_impl(quote! { | 
 |     ///         fn foobar() {} | 
 |     ///     }), | 
 |     ///     quote! { | 
 |     ///         fn macro_rewritten_foobar() {} | 
 |     ///     } | 
 |     /// ); | 
 |     /// ``` | 
 |     macro_rules! assert_syn_file { | 
 |         ($actual:expr, $expected:expr) => { | 
 |             let actual = syn::parse2::<syn::File>($actual).unwrap(); | 
 |             let expected: syn::File = $expected; | 
 |             assert!( | 
 |                 actual == expected, | 
 |                 "{}", | 
 |                 pretty_assertions::StrComparison::new( | 
 |                     &prettyplease::unparse(&expected), | 
 |                     &prettyplease::unparse(&actual), | 
 |                 ) | 
 |             ) | 
 |         }; | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn test_fuzztest_expansion() { | 
 |         assert_syn_file!( | 
 |             fuzztest_impl( | 
 |                 quote! {}, | 
 |                 quote! { | 
 |                     fn foobar(input: &[u8]) { | 
 |                         panic!("I am just a test") | 
 |                     } | 
 |                 } | 
 |             ) | 
 |             .unwrap(), | 
 |             parse_quote! { | 
 |                 #[allow(unused)] | 
 |                 fn foobar(input: &[u8]) { | 
 |                     panic!("I am just a test") | 
 |                 } | 
 |  | 
 |                 #[automatically_derived] | 
 |                 #[cfg(fuzzing)] | 
 |                 ::libfuzzer_sys::fuzz_target!(|args: (&[u8])| { | 
 |                     let (input) = args; | 
 |                     foobar(input) | 
 |                 }); | 
 |  | 
 |                 #[cfg(not(any(fuzzing, rust_analyzer)))] | 
 |                 fn main() { | 
 |                     ::std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); | 
 |                 } | 
 |  | 
 |                 #[automatically_derived] | 
 |                 #[cfg(test)] | 
 |                 mod proptest_foobar { | 
 |                     use super::*; | 
 |                     use ::derive_fuzztest::reexport::proptest; | 
 |                     use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; | 
 |                     proptest::proptest! { | 
 |                         #![proptest_config(proptest::prelude::ProptestConfig { | 
 |                             cases: 1024, | 
 |                             failure_persistence: Some(Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression"))), | 
 |                             ..Default::default() | 
 |                         })] | 
 |                         #[test] | 
 |                         fn proptest_foobar(args in arb::<(&[u8])>()) { | 
 |                             let (input) = args; | 
 |                             foobar(input); | 
 |                         } | 
 |                     } | 
 |                 } | 
 |  | 
 |                 #[automatically_derived] | 
 |                 #[test] | 
 |                 fn quickcheck_foobar() { | 
 |                     use ::derive_fuzztest::reexport::quickcheck::TestResult; | 
 |                     use ::derive_fuzztest::arbitrary_bridge::ArbitraryAdapter; | 
 |  | 
 |                     fn inner(args: (ArbitraryAdapter<&[u8]>)) -> TestResult { | 
 |                         let (ArbitraryAdapter(::core::result::Result::Ok(input))) = args else { | 
 |                             return TestResult::discard() | 
 |                         }; | 
 |                         match ::std::panic::catch_unwind(move || { | 
 |                             foobar(input); | 
 |                         }) { | 
 |                             ::core::result::Result::Ok(()) => TestResult::passed(), | 
 |                             ::core::result::Result::Err(e) => TestResult::error(::std::format!("{e:?}")), | 
 |                         } | 
 |                     } | 
 |                     ::derive_fuzztest::reexport::quickcheck::QuickCheck::new() | 
 |                         .tests(1024) | 
 |                         .quickcheck(inner as fn(_) -> TestResult); | 
 |                 } | 
 |             } | 
 |         ); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn test_fuzz_expansion() { | 
 |         assert_syn_file!( | 
 |             fuzz_impl( | 
 |                 quote! {}, | 
 |                 quote! { | 
 |                     fn foobar(input: &[u8]) { | 
 |                         panic!("I am just a test") | 
 |                     } | 
 |                 } | 
 |             ) | 
 |             .unwrap(), | 
 |             parse_quote! { | 
 |                 #[allow(unused)] | 
 |                 fn foobar(input: &[u8]) { | 
 |                     panic!("I am just a test") | 
 |                 } | 
 |  | 
 |                 #[automatically_derived] | 
 |                 #[cfg(fuzzing)] | 
 |                 ::libfuzzer_sys::fuzz_target!(|args: (&[u8])| { | 
 |                     let (input) = args; | 
 |                     foobar(input) | 
 |                 }); | 
 |  | 
 |                 #[cfg(not(any(fuzzing, rust_analyzer)))] | 
 |                 fn main() { | 
 |                     ::std::unreachable!("Run this target with `cargo fuzz` or `cargo test` instead"); | 
 |                 } | 
 |             } | 
 |         ); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn test_proptest_expansion() { | 
 |         assert_syn_file!( | 
 |             proptest_impl( | 
 |                 quote! {}, | 
 |                 quote! { | 
 |                     fn foobar(input: &[u8]) { | 
 |                         panic!("I am just a test") | 
 |                     } | 
 |                 } | 
 |             ) | 
 |             .unwrap(), | 
 |             parse_quote! { | 
 |                 #[allow(unused)] | 
 |                 fn foobar(input: &[u8]) { | 
 |                     panic!("I am just a test") | 
 |                 } | 
 |  | 
 |                 #[automatically_derived] | 
 |                 #[cfg(test)] | 
 |                 mod proptest_foobar { | 
 |                     use super::*; | 
 |                     use ::derive_fuzztest::reexport::proptest; | 
 |                     use ::derive_fuzztest::reexport::proptest_arbitrary_interop::arb; | 
 |                     proptest::proptest! { | 
 |                         #![proptest_config(proptest::prelude::ProptestConfig { | 
 |                             cases: 1024, | 
 |                             failure_persistence: Some(Box::new(proptest::test_runner::FileFailurePersistence::WithSource("regression"))), | 
 |                             ..Default::default() | 
 |                         })] | 
 |                         #[test] | 
 |                         fn proptest_foobar(args in arb::<(&[u8])>()) { | 
 |                             let (input) = args; | 
 |                             foobar(input); | 
 |                         } | 
 |                     } | 
 |                 } | 
 |             } | 
 |         ); | 
 |     } | 
 | } |