diff --git a/Cargo.lock b/Cargo.lock index 1584a53..ca0a5b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ [[package]] name = "auto-fuzz-test" -version = "0.2.3" +version = "0.2.4" dependencies = [ "assert_tokens_eq", "cargo_toml", diff --git a/Cargo.toml b/Cargo.toml index b529213..dc740bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "auto-fuzz-test" -version = "0.2.3" +version = "0.2.4" authors = ["Eh2406 ", "Sergey \"Shnatsel\" Davidoff ","Eugene Lomov "] edition = "2018" diff --git a/src/crate_parse.rs b/src/crate_parse.rs index 8859a30..ba4e9a4 100644 --- a/src/crate_parse.rs +++ b/src/crate_parse.rs @@ -24,11 +24,14 @@ impl CrateInfo { let mut entries = path.read_dir().ok()?; let cargo_toml_present = entries.any(|result| { result - .map(|entry| entry.file_name().to_string_lossy() == "Cargo.toml") + .map(|entry| match entry.file_name().to_str() { + Some(filename) => filename == "Cargo.toml", + None => false, + }) .unwrap_or(false) }); if cargo_toml_present { - if let Some(crate_name) = parse_crate_name(&path.join("Cargo.toml")) { + if let Some(crate_name) = CrateInfo::parse_crate_name(&path.join("Cargo.toml")) { Some(CrateInfo { crate_root: path.to_path_buf(), crate_name, @@ -46,31 +49,12 @@ impl CrateInfo { } pub fn fuzz_dir(&self) -> std::io::Result { - let fuzz_dir_path = self.crate_root.join("fuzz"); - let fuzz_targets_dir_path = self.crate_root.join("fuzz").join("fuzz_targets"); - match std::fs::create_dir(&fuzz_dir_path) { - Ok(_) => match std::fs::create_dir(&fuzz_targets_dir_path) { - Ok(_) => Ok(fuzz_targets_dir_path), - Err(e) => { - if e.kind() == std::io::ErrorKind::AlreadyExists { - Ok(fuzz_targets_dir_path) - } else { - Err(e) - } - } - }, + let fuzz_dir_path = self.crate_root.join("fuzz").join("fuzz_targets"); + match std::fs::create_dir_all(&fuzz_dir_path) { + Ok(_) => Ok(fuzz_dir_path), Err(e) => { if e.kind() == std::io::ErrorKind::AlreadyExists { - match std::fs::create_dir(&fuzz_targets_dir_path) { - Ok(_) => Ok(fuzz_targets_dir_path), - Err(e) => { - if e.kind() == std::io::ErrorKind::AlreadyExists { - Ok(fuzz_targets_dir_path) - } else { - Err(e) - } - } - } + Ok(fuzz_dir_path) } else { Err(e) } @@ -78,75 +62,41 @@ impl CrateInfo { } } - pub fn write_cargo_toml( + pub fn add_target_to_cargo_toml( &self, function: &Ident, impl_type: Option<&Type>, - attr: &TokenStream, + module_path: &TokenStream, ) -> Result<(), Error> { - // This is used to distinguish functions with the same names but in different modules - let ident = match impl_type { - Some(typ) => { - if let Type::Path(path) = typ { - if attr.is_empty() { - format!( - "{}_{}", - &(path.path.segments.iter().next().unwrap().ident).to_string(), - &function.to_string() - ) - } else { - format!( - "{}__{}_{}", - attr.to_string().replace(" :: ", "__"), - &(path.path.segments.iter().next().unwrap().ident).to_string(), - &function.to_string() - ) - } - } else { - panic!("Complex self type.") - } - } - None => { - if attr.is_empty() { - function.to_string() - } else { - format!( - "{}__{}", - attr.to_string().replace(" :: ", "__"), - &function.to_string() - ) - } - } - }; + let ident = construct_harness_ident(function, impl_type, module_path); - match OpenOptions::new().write(true).create_new(true).open( - self.fuzz_dir() - .unwrap() - .parent() - .unwrap() - .join("Cargo.toml"), - ) { + let cargo_toml_path = self.fuzz_dir()?.parent().unwrap().join("Cargo.toml"); + match OpenOptions::new() + .write(true) + .create_new(true) + .open(&cargo_toml_path) + { Ok(mut file) => { file.lock_exclusive()?; write!( file, "{}{}{}{}{}", - &CARGO_TOML_TEMPLATE_PREFIX, + &CrateInfo::CARGO_TOML_TEMPLATE_PREFIX, &self.crate_name(), - &CARGO_TOML_TEMPLATE_INFIX, + &CrateInfo::CARGO_TOML_TEMPLATE_INFIX, &self.crate_name(), - &CARGO_TOML_TEMPLATE_POSTFIX + &CrateInfo::CARGO_TOML_TEMPLATE_POSTFIX )?; write!( file, "{}{}{}{}{}", - &TARGET_TEMPLATE_PREFIX, + &CrateInfo::TARGET_TEMPLATE_PREFIX, &ident, - &TARGET_TEMPLATE_INFIX, + &CrateInfo::TARGET_TEMPLATE_INFIX, &ident, - &TARGET_TEMPLATE_POSTFIX + &CrateInfo::TARGET_TEMPLATE_POSTFIX )?; file.flush()?; @@ -159,26 +109,21 @@ impl CrateInfo { .read(true) .write(true) .append(true) - .open( - self.fuzz_dir() - .unwrap() - .parent() - .unwrap() - .join("Cargo.toml"), - )?; + .open(&cargo_toml_path)?; file.lock_exclusive()?; // Checking, that we are not going to duplicate [[bin]] targets let mut buffer = String::new(); file.read_to_string(&mut buffer)?; + // Generated Cargo.toml consists of several sections splitted by '\n\n' + // Here we split and skip the first 5 of them: [package], [package.metadata], [dependencies], [dependencies.] and [workspace] let parts = buffer.split("\n\n"); - let fuzz_target_exists = parts - .skip(5) - .map(|item| { - if let TomlTable(table) = - &item.lines().nth(1).unwrap().parse::().unwrap() - { - if let TomlString(s) = table.get("name").unwrap() { + let fuzz_target_exists = parts.skip(5).any(|item| { + // In this closure we extract target ident and compare it with the one we want to add. + // If anything goes wrong, this closure returns `false`. + if let Some(target_name_line) = item.lines().nth(1) { + if let Ok(TomlTable(table)) = &target_name_line.parse::() { + if let Some(TomlString(s)) = table.get("name") { s == &ident } else { false @@ -186,17 +131,19 @@ impl CrateInfo { } else { false } - }) - .fold(false, |acc, x| acc | x); + } else { + false + } + }); if !fuzz_target_exists { write!( file, "{}{}{}{}{}", - &TARGET_TEMPLATE_PREFIX, + &CrateInfo::TARGET_TEMPLATE_PREFIX, &ident, - &TARGET_TEMPLATE_INFIX, + &CrateInfo::TARGET_TEMPLATE_INFIX, &ident, - &TARGET_TEMPLATE_POSTFIX + &CrateInfo::TARGET_TEMPLATE_POSTFIX )?; file.flush()?; } @@ -209,33 +156,32 @@ impl CrateInfo { } } } -} -fn parse_crate_name(cargo_toml_path: &Path) -> Option { - let cargo_bytes = { - let mut cargo_bytes = Vec::new(); - File::open(cargo_toml_path) - .ok()? - .read_to_end(&mut cargo_bytes) - .ok()?; - cargo_bytes - }; - - let cargo_toml: TomlValue = toml::from_slice(&cargo_bytes).ok()?; - - Some( - cargo_toml - .get("package")? - .get("name")? - .as_str()? - .to_string(), - ) -} + fn parse_crate_name(cargo_toml_path: &Path) -> Option { + let cargo_bytes = { + let mut cargo_bytes = Vec::new(); + File::open(cargo_toml_path) + .ok()? + .read_to_end(&mut cargo_bytes) + .ok()?; + cargo_bytes + }; + + let cargo_toml: TomlValue = toml::from_slice(&cargo_bytes).ok()?; -const CARGO_TOML_TEMPLATE_PREFIX: &str = r#"[package] + Some( + cargo_toml + .get("package")? + .get("name")? + .as_str()? + .to_string(), + ) + } + + const CARGO_TOML_TEMPLATE_PREFIX: &'static str = r#"[package] name = ""#; -const CARGO_TOML_TEMPLATE_INFIX: &str = r#"-fuzz" + const CARGO_TOML_TEMPLATE_INFIX: &'static str = r#"-fuzz" version = "0.0.0" authors = ["Automatically generated"] publish = false @@ -249,7 +195,7 @@ libfuzzer-sys = "0.4" [dependencies."#; -const CARGO_TOML_TEMPLATE_POSTFIX: &str = r#"] + const CARGO_TOML_TEMPLATE_POSTFIX: &'static str = r#"] path = ".." # Prevent this from interfering with workspaces @@ -257,17 +203,60 @@ path = ".." members = ["."] "#; -const TARGET_TEMPLATE_PREFIX: &str = r#" + const TARGET_TEMPLATE_PREFIX: &'static str = r#" [[bin]] name = ""#; -const TARGET_TEMPLATE_INFIX: &str = r#"" + const TARGET_TEMPLATE_INFIX: &'static str = r#"" path = "fuzz_targets/"#; -const TARGET_TEMPLATE_POSTFIX: &str = r#".rs" + const TARGET_TEMPLATE_POSTFIX: &'static str = r#".rs" test = false doc = false "#; +} + +pub fn construct_harness_ident( + function: &Ident, + impl_type: Option<&Type>, + module_path: &TokenStream, +) -> String { + // Functions in different modules and/or in different impl's can have identical names. To + // avoid collisions, this function adds module path and impl type to target filenames. + match impl_type { + Some(typ) => { + if let Type::Path(path) = typ { + if module_path.is_empty() { + format!( + "{}_{}", + &(path.path.segments.iter().next().unwrap().ident).to_string(), + &function.to_string() + ) + } else { + format!( + "{}__{}_{}", + module_path.to_string().replace(" :: ", "__"), + &(path.path.segments.iter().next().unwrap().ident).to_string(), + &function.to_string() + ) + } + } else { + unimplemented!("Complex self types.") + } + } + None => { + if module_path.is_empty() { + function.to_string() + } else { + format!( + "{}__{}", + module_path.to_string().replace(" :: ", "__"), + &function.to_string() + ) + } + } + } +} #[cfg(test)] impl PartialEq for CrateInfo { @@ -299,7 +288,7 @@ mod tests { let cargo_toml_path = dir.path().join("Cargo.toml"); File::create(&cargo_toml_path).expect("Could not create Cargo.toml fot test"); - assert_eq!(parse_crate_name(&cargo_toml_path), None); + assert_eq!(CrateInfo::parse_crate_name(&cargo_toml_path), None); } #[test] @@ -312,7 +301,7 @@ mod tests { .expect("Could not write valid data to Cargo.toml fot test"); assert_eq!( - parse_crate_name(&cargo_toml_path), + CrateInfo::parse_crate_name(&cargo_toml_path), Some("test-lib".to_string()) ); } @@ -334,7 +323,7 @@ mod tests { } #[test] - fn write_cargo_noattr_noimpl() { + fn write_cargo_nomodule_noimpl() { let dir = tempdir().expect("Could not create tempdir fot test"); let cargo_toml_path = dir.path().join("Cargo.toml"); let mut cargo_toml = @@ -344,20 +333,24 @@ mod tests { let crate_info = CrateInfo::from_root(dir.path()).unwrap(); let ident = format_ident!("foo"); - let attr = TokenStream::new(); + let module = TokenStream::new(); - crate_info.write_cargo_toml(&ident, None, &attr).unwrap(); + crate_info + .add_target_to_cargo_toml(&ident, None, &module) + .unwrap(); - crate_info.write_cargo_toml(&ident, None, &attr).unwrap(); + crate_info + .add_target_to_cargo_toml(&ident, None, &module) + .unwrap(); let mut cargo_toml = File::open(dir.path().join("fuzz").join("Cargo.toml")).unwrap(); let mut cargo_contents = String::new(); cargo_toml.read_to_string(&mut cargo_contents).unwrap(); - assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_NOATTR_NOIMPL); + assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_NOMODULE_NOIMPL); } #[test] - fn write_cargo_attr_noimpl() { + fn write_cargo_module_noimpl() { let dir = tempdir().expect("Could not create tempdir fot test"); let cargo_toml_path = dir.path().join("Cargo.toml"); let mut cargo_toml = @@ -367,20 +360,24 @@ mod tests { let crate_info = CrateInfo::from_root(dir.path()).unwrap(); let ident = format_ident!("cat"); - let attr = quote!(foo::bar::dog); + let module = quote!(foo::bar::dog); - crate_info.write_cargo_toml(&ident, None, &attr).unwrap(); + crate_info + .add_target_to_cargo_toml(&ident, None, &module) + .unwrap(); - crate_info.write_cargo_toml(&ident, None, &attr).unwrap(); + crate_info + .add_target_to_cargo_toml(&ident, None, &module) + .unwrap(); let mut cargo_toml = File::open(dir.path().join("fuzz").join("Cargo.toml")).unwrap(); let mut cargo_contents = String::new(); cargo_toml.read_to_string(&mut cargo_contents).unwrap(); - assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_ATTR_NOIMPL); + assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_MODULE_NOIMPL); } #[test] - fn write_cargo_noattr_impl() { + fn write_cargo_nomodule_impl() { let dir = tempdir().expect("Could not create tempdir fot test"); let cargo_toml_path = dir.path().join("Cargo.toml"); let mut cargo_toml = @@ -395,23 +392,23 @@ mod tests { } }) .unwrap(); - let attr = TokenStream::new(); + let module = TokenStream::new(); crate_info - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); crate_info - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); let mut cargo_toml = File::open(dir.path().join("fuzz").join("Cargo.toml")).unwrap(); let mut cargo_contents = String::new(); cargo_toml.read_to_string(&mut cargo_contents).unwrap(); - assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_NOATTR_IMPL); + assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_NOMODULE_IMPL); } #[test] - fn write_cargo_attr_impl() { + fn write_cargo_module_impl() { let dir = tempdir().expect("Could not create tempdir fot test"); let cargo_toml_path = dir.path().join("Cargo.toml"); let mut cargo_toml = @@ -426,19 +423,19 @@ mod tests { } }) .unwrap(); - let attr = quote!(foo::bar::dog); + let module = quote!(foo::bar::dog); crate_info - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); crate_info - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); let mut cargo_toml = File::open(dir.path().join("fuzz").join("Cargo.toml")).unwrap(); let mut cargo_contents = String::new(); cargo_toml.read_to_string(&mut cargo_contents).unwrap(); - assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_ATTR_IMPL); + assert_eq!(cargo_contents, VALID_GENERATED_CARGO_TOML_MODULE_IMPL); } #[test] @@ -474,78 +471,78 @@ mod tests { let handle_1 = thread::spawn(move || { // 1 let ident = format_ident!("foo"); - let attr = quote!(); + let module = quote!(); crate_info_thread_1 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); // 3 let ident = format_ident!("foo"); - let attr = quote!(foo); + let module = quote!(foo); crate_info_thread_1 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); // 5 let ident = format_ident!("cat"); - let attr = quote!(foo::bar::dog); + let module = quote!(foo::bar::dog); crate_info_thread_1 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); // 7 let ident = format_ident!("bar"); - let attr = quote!(); + let module = quote!(); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_1 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 3 let ident = format_ident!("foo"); - let attr = quote!(foo); + let module = quote!(foo); crate_info_thread_1 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); // 7 let ident = format_ident!("foo"); - let attr = quote!(); + let module = quote!(); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_1 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 9 let ident = format_ident!("foo"); - let attr = quote!(foo); + let module = quote!(foo); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_1 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 11 let ident = format_ident!("foo"); - let attr = quote!(foo::bar); + let module = quote!(foo::bar); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_1 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); }); @@ -553,114 +550,118 @@ mod tests { let handle_2 = thread::spawn(move || { // 6 let ident = format_ident!("dog"); - let attr = quote!(foo::bar::dog); + let module = quote!(foo::bar::dog); crate_info_thread_2 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); // 8 let ident = format_ident!("bar"); - let attr = quote!(); + let module = quote!(); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_2 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 10 let ident = format_ident!("bar"); - let attr = quote!(foo); + let module = quote!(foo); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_2 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 12 let ident = format_ident!("bar"); - let attr = quote!(foo::bar); + let module = quote!(foo::bar); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_2 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 6 let ident = format_ident!("dog"); - let attr = quote!(foo::bar::dog); + let module = quote!(foo::bar::dog); crate_info_thread_2 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); // 10 let ident = format_ident!("bar"); - let attr = quote!(foo); + let module = quote!(foo); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info_thread_2 - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 2 let ident = format_ident!("bar"); - let attr = quote!(); + let module = quote!(); crate_info_thread_2 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); // 4 let ident = format_ident!("bar"); - let attr = quote!(foo); + let module = quote!(foo); crate_info_thread_2 - .write_cargo_toml(&ident, None, &attr) + .add_target_to_cargo_toml(&ident, None, &module) .unwrap(); }); { // 9 let ident = format_ident!("foo"); - let attr = quote!(foo); + let module = quote!(foo); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); // 2 let ident = format_ident!("bar"); - let attr = quote!(); - crate_info.write_cargo_toml(&ident, None, &attr).unwrap(); + let module = quote!(); + crate_info + .add_target_to_cargo_toml(&ident, None, &module) + .unwrap(); // 4 let ident = format_ident!("bar"); - let attr = quote!(foo); - crate_info.write_cargo_toml(&ident, None, &attr).unwrap(); + let module = quote!(foo); + crate_info + .add_target_to_cargo_toml(&ident, None, &module) + .unwrap(); // 11 let ident = format_ident!("foo"); - let attr = quote!(foo::bar); + let module = quote!(foo::bar); let implementation: ItemImpl = syn::parse2(quote! { impl TestStruct { } }) .unwrap(); crate_info - .write_cargo_toml(&ident, Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml(&ident, Some(&implementation.self_ty), &module) .unwrap(); } @@ -710,7 +711,7 @@ auto-fuzz-test = { path = "../" } arbitrary = { version = "1", features = ["derive"] } "#; - const VALID_GENERATED_CARGO_TOML_NOATTR_NOIMPL: &str = r#"[package] + const VALID_GENERATED_CARGO_TOML_NOMODULE_NOIMPL: &str = r#"[package] name = "test-lib-fuzz" version = "0.0.0" authors = ["Automatically generated"] @@ -737,7 +738,7 @@ test = false doc = false "#; - const VALID_GENERATED_CARGO_TOML_ATTR_NOIMPL: &str = r#"[package] + const VALID_GENERATED_CARGO_TOML_MODULE_NOIMPL: &str = r#"[package] name = "test-lib-fuzz" version = "0.0.0" authors = ["Automatically generated"] @@ -764,7 +765,7 @@ test = false doc = false "#; - const VALID_GENERATED_CARGO_TOML_NOATTR_IMPL: &str = r#"[package] + const VALID_GENERATED_CARGO_TOML_NOMODULE_IMPL: &str = r#"[package] name = "test-lib-fuzz" version = "0.0.0" authors = ["Automatically generated"] @@ -791,7 +792,7 @@ test = false doc = false "#; - const VALID_GENERATED_CARGO_TOML_ATTR_IMPL: &str = r#"[package] + const VALID_GENERATED_CARGO_TOML_MODULE_IMPL: &str = r#"[package] name = "test-lib-fuzz" version = "0.0.0" authors = ["Automatically generated"] diff --git a/src/generate.rs b/src/generate.rs index ee9d6f8..4387eaa 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -6,10 +6,7 @@ use syn::{ Signature, Stmt, Type, }; -pub fn fuzz_struct( - signature: &Signature, - impl_type: Option<&Type>, -) -> Result { +pub fn fuzz_struct(signature: &Signature, impl_type: Option<&Type>) -> Result { // struct for function arguments template let mut fuzz_struct: ItemStruct = syn::parse2(quote! { #[derive(Arbitrary)] @@ -31,7 +28,7 @@ pub fn fuzz_struct( &(*signature).ident.to_string() ) } else { - return Err(GenerateError::ComplexSelfType); + return Err(Error::ComplexSelfType); } } None => { @@ -41,6 +38,9 @@ pub fn fuzz_struct( // Struct fields generation if let Fields::Named(ref mut fields) = fuzz_struct.fields { + // Here comes an epic destructuring of syn types. + // `else` parts of match arms on known data (`fuzz_struct` in this case) are + // marked with unreachable! macro, as they are, ahem, unreachable. let default_boxed_variable = fields .named .pop() @@ -85,18 +85,18 @@ pub fn fuzz_struct( { *new_subpath = Type::Path(path); } else { - panic!("Wrong boxed variable template"); + unreachable!("Wrong boxed variable template"); } } else { - panic!("Wrong boxed variable template"); + unreachable!("Wrong boxed variable template"); } } else { - panic!("Wrong boxed variable template"); + unreachable!("Wrong boxed variable template"); } // Pushing variable type for the struct field fields.named.push(variable); } else { - return Err(GenerateError::ComplexArg); + return Err(Error::ComplexArg); } } Type::Path(path) => { @@ -109,11 +109,11 @@ pub fn fuzz_struct( fields.named.push(variable); } _ => { - return Err(GenerateError::ComplexArg); + return Err(Error::ComplexArg); } }; } else { - return Err(GenerateError::ComplexVariable); + return Err(Error::ComplexVariable); } } FnArg::Receiver(res) => { @@ -134,13 +134,13 @@ pub fn fuzz_struct( { *new_subpath = (*impl_type).clone(); } else { - panic!("Wrong boxed variable template"); + unreachable!("Wrong boxed variable template"); } } else { - panic!("Wrong boxed variable template"); + unreachable!("Wrong boxed variable template"); } } else { - panic!("Wrong boxed variable template"); + unreachable!("Wrong boxed variable template"); } // Pushing variable type for the struct field fields.named.push(variable); @@ -154,7 +154,7 @@ pub fn fuzz_struct( fields.named.push(variable); } } else { - return Err(GenerateError::ComplexSelfType); + return Err(Error::ComplexSelfType); } } else { panic!("Self type must be supplied for method parsing") @@ -163,284 +163,279 @@ pub fn fuzz_struct( } } } else { - panic!("Struct template must contain named fields"); + unreachable!("Struct template must contain named fields"); } Ok(fuzz_struct) } -pub fn fuzz_function( - signature: &Signature, - impl_type: Option<&Type>, -) -> Result { +pub fn fuzz_function(signature: &Signature, impl_type: Option<&Type>) -> Result { // Checking that the function meets our requirements if signature.asyncness.is_some() { - return Err(GenerateError::Async); + return Err(Error::Async); } if signature.unsafety.is_some() { - return Err(GenerateError::Unsafe); + return Err(Error::Unsafe); } if signature.inputs.is_empty() { - return Err(GenerateError::Empty); + return Err(Error::Empty); } let mut fuzz_function: syn::ItemFn; - match impl_type { - Some(typ) => { - match (*signature).inputs.first().unwrap() { - FnArg::Receiver(_) => { - // method harness template - fuzz_function = syn::parse2(quote! { - pub fn fuzz(mut input:MyStruct) { - (input.slf).foo(input.a, &mut *input.b); - } - }) - .unwrap(); - - if let Stmt::Semi(Expr::MethodCall(method_call), _) = - &mut fuzz_function.block.stmts[0] - { - // MethodCall inside fuzzing function - method_call.method = (*signature).ident.clone(); - - // Arguments for internal method call - let args = &mut method_call.args; - let default_borrowed_field = args.pop().unwrap().into_value(); - let default_field = args.pop().unwrap().into_value(); - - for item in (*signature).inputs.iter().skip(1) { - match item { - FnArg::Typed(i) => { - if let Pat::Ident(id) = &*i.pat { - match *i.ty.clone() { - Type::Reference(rf) => { - let mut new_field = default_borrowed_field.clone(); - if let Expr::Reference(ref mut new_rf) = new_field { - // Copying borrow mutability - new_rf.mutability = rf.mutability; - // Copying field ident - if let Expr::Unary(ref mut new_subfield) = - *new_rf.expr + if let Some(typ) = impl_type { + match (*signature).inputs.first().unwrap() { + FnArg::Receiver(_) => { + // method harness template + fuzz_function = syn::parse2(quote! { + pub fn fuzz(mut input:MyStruct) { + (input.slf).foo(input.a, &mut *input.b); + } + }) + .unwrap(); + + if let Stmt::Semi(Expr::MethodCall(method_call), _) = + &mut fuzz_function.block.stmts[0] + { + // MethodCall inside fuzzing function + method_call.method = (*signature).ident.clone(); + + // Arguments for internal method call + let args = &mut method_call.args; + let default_borrowed_field = args.pop().unwrap().into_value(); + let default_field = args.pop().unwrap().into_value(); + + for item in (*signature).inputs.iter().skip(1) { + match item { + FnArg::Typed(i) => { + if let Pat::Ident(id) = &*i.pat { + match *i.ty.clone() { + Type::Reference(rf) => { + let mut new_field = default_borrowed_field.clone(); + if let Expr::Reference(ref mut new_rf) = new_field { + // Copying borrow mutability + new_rf.mutability = rf.mutability; + // Copying field ident + if let Expr::Unary(ref mut new_subfield) = + *new_rf.expr + { + if let Expr::Field(ref mut new_unary_subfield) = + *new_subfield.expr { - if let Expr::Field( - ref mut new_unary_subfield, - ) = *new_subfield.expr - { - new_unary_subfield.member = - Member::Named(id.ident.clone()); - } else { - panic!("Wrong borrowed field template"); - } + new_unary_subfield.member = + Member::Named(id.ident.clone()); } else { - panic!("Wrong borrowed field template"); + unreachable!( + "Wrong borrowed field template" + ); } } else { - panic!("Wrong borrowed field template"); - } - - // Pushing arguments to the function call - args.push(new_field); - } - Type::Path(_) => { - let mut new_field = default_field.clone(); - if let Expr::Field(ref mut f) = new_field { - f.member = Member::Named(id.ident.clone()); - } else { - panic!("Wrong unborrowed field template"); + unreachable!("Wrong borrowed field template"); } - // Pushing arguments to the function call - args.push(new_field); + } else { + unreachable!("Wrong borrowed field template"); } - _ => { - return Err(GenerateError::ComplexArg); + + // Pushing arguments to the function call + args.push(new_field); + } + Type::Path(_) => { + let mut new_field = default_field.clone(); + if let Expr::Field(ref mut f) = new_field { + f.member = Member::Named(id.ident.clone()); + } else { + unreachable!("Wrong unborrowed field template"); } - }; - } else { - return Err(GenerateError::ComplexSelfType); - } - } - FnArg::Receiver(_) => { - return Err(GenerateError::MultipleRes); + // Pushing arguments to the function call + args.push(new_field); + } + _ => { + return Err(Error::ComplexArg); + } + }; + } else { + return Err(Error::ComplexSelfType); } } + FnArg::Receiver(_) => { + return Err(Error::MultipleRes); + } } - } else { - panic!("Wrong method call template.") } + } else { + unreachable!("Wrong method call template.") } - FnArg::Typed(_) => { - // method harness template - fuzz_function = syn::parse2(quote! { - pub fn fuzz(mut input:MyStruct) { - MyType::foo(input.a, &mut *input.b); - } - }) - .unwrap(); - if let Stmt::Semi(Expr::Call(fn_call), _) = &mut fuzz_function.block.stmts[0] { - // FnCall inside fuzzing function - if let Expr::Path(path) = &mut *fn_call.func { - let mut segments_iter = path.path.segments.iter_mut(); - if let Type::Path(type_path) = typ { - segments_iter.next().unwrap().ident = - type_path.path.segments.first().unwrap().ident.clone(); - } else { - return Err(GenerateError::ComplexMethodCall); - } - segments_iter.next().unwrap().ident = (*signature).ident.clone(); + } + FnArg::Typed(_) => { + // method harness template + fuzz_function = syn::parse2(quote! { + pub fn fuzz(mut input:MyStruct) { + MyType::foo(input.a, &mut *input.b); + } + }) + .unwrap(); + + if let Stmt::Semi(Expr::Call(fn_call), _) = &mut fuzz_function.block.stmts[0] { + // FnCall inside fuzzing function + if let Expr::Path(path) = &mut *fn_call.func { + let mut segments_iter = path.path.segments.iter_mut(); + if let Type::Path(type_path) = typ { + segments_iter.next().unwrap().ident = + type_path.path.segments.first().unwrap().ident.clone(); + } else { + return Err(Error::ComplexMethodCall); } + segments_iter.next().unwrap().ident = (*signature).ident.clone(); + } - // Arguments for internal function call - let args = &mut fn_call.args; - let default_borrowed_field = args.pop().unwrap().into_value(); - let default_field = args.pop().unwrap().into_value(); - - for item in (*signature).inputs.iter() { - match item { - FnArg::Typed(i) => { - if let Pat::Ident(id) = &*i.pat { - match *i.ty.clone() { - Type::Reference(rf) => { - let mut new_field = default_borrowed_field.clone(); - if let Expr::Reference(ref mut new_rf) = new_field { - // Copying borrow mutability - new_rf.mutability = rf.mutability; - // Copying field ident - if let Expr::Unary(ref mut new_subfield) = - *new_rf.expr + // Arguments for internal function call + let args = &mut fn_call.args; + let default_borrowed_field = args.pop().unwrap().into_value(); + let default_field = args.pop().unwrap().into_value(); + + for item in (*signature).inputs.iter() { + match item { + FnArg::Typed(i) => { + if let Pat::Ident(id) = &*i.pat { + match *i.ty.clone() { + Type::Reference(rf) => { + let mut new_field = default_borrowed_field.clone(); + if let Expr::Reference(ref mut new_rf) = new_field { + // Copying borrow mutability + new_rf.mutability = rf.mutability; + // Copying field ident + if let Expr::Unary(ref mut new_subfield) = + *new_rf.expr + { + if let Expr::Field(ref mut new_unary_subfield) = + *new_subfield.expr { - if let Expr::Field( - ref mut new_unary_subfield, - ) = *new_subfield.expr - { - new_unary_subfield.member = - Member::Named(id.ident.clone()); - } else { - panic!("Wrong borrowed field template"); - } + new_unary_subfield.member = + Member::Named(id.ident.clone()); } else { - panic!("Wrong borrowed field template"); + unreachable!( + "Wrong borrowed field template" + ); } } else { - panic!("Wrong borrowed field template"); - } - - // Pushing arguments to the function call - args.push(new_field); - } - Type::Path(_) => { - let mut new_field = default_field.clone(); - if let Expr::Field(ref mut f) = new_field { - f.member = Member::Named(id.ident.clone()); - } else { - panic!("Wrong unborrowed field template"); + unreachable!("Wrong borrowed field template"); } - // Pushing arguments to the function call - args.push(new_field); + } else { + unreachable!("Wrong borrowed field template"); } - _ => { - return Err(GenerateError::ComplexArg); + + // Pushing arguments to the function call + args.push(new_field); + } + Type::Path(_) => { + let mut new_field = default_field.clone(); + if let Expr::Field(ref mut f) = new_field { + f.member = Member::Named(id.ident.clone()); + } else { + unreachable!("Wrong unborrowed field template"); } - }; - } else { - return Err(GenerateError::ComplexSelfType); - } - } - FnArg::Receiver(_) => { - panic!( - "This macros can not be used for fuzzing methods, use #[create_cargofuzz_impl_harness]" - ) + // Pushing arguments to the function call + args.push(new_field); + } + _ => { + return Err(Error::ComplexArg); + } + }; + } else { + return Err(Error::ComplexSelfType); } } + FnArg::Receiver(_) => { + panic!( + "This macros can not be used for fuzzing methods, use #[create_cargofuzz_impl_harness]" + ) + } } - } else { - panic!("Wrong generator call template.") } + } else { + unreachable!("Wrong generator call template.") } } } - None => { - // function harness template - fuzz_function = syn::parse2(quote! { - pub fn fuzz(mut input:MyStruct) { - foo(input.a, &mut *input.b); - } - }) - .unwrap(); - - if let Stmt::Semi(Expr::Call(fn_call), _) = &mut fuzz_function.block.stmts[0] { - // FnCall inside fuzzing function - if let Expr::Path(path) = &mut *fn_call.func { - path.path.segments.iter_mut().next().unwrap().ident = - (*signature).ident.clone(); - } else { - panic!("Wrong function harness template.") - } + } else { + // function harness template + fuzz_function = syn::parse2(quote! { + pub fn fuzz(mut input:MyStruct) { + foo(input.a, &mut *input.b); + } + }) + .unwrap(); - // Arguments for internal function call - let args = &mut fn_call.args; - let default_borrowed_field = args.pop().unwrap().into_value(); - let default_field = args.pop().unwrap().into_value(); - - for item in (*signature).inputs.iter() { - match item { - FnArg::Typed(i) => { - if let Pat::Ident(id) = &*i.pat { - match *i.ty.clone() { - Type::Reference(rf) => { - let mut new_field = default_borrowed_field.clone(); - if let Expr::Reference(ref mut new_rf) = new_field { - // Copying borrow mutability - new_rf.mutability = rf.mutability; - // Copying field ident - if let Expr::Unary(ref mut new_subfield) = *new_rf.expr + if let Stmt::Semi(Expr::Call(fn_call), _) = &mut fuzz_function.block.stmts[0] { + // FnCall inside fuzzing function + if let Expr::Path(path) = &mut *fn_call.func { + path.path.segments.iter_mut().next().unwrap().ident = (*signature).ident.clone(); + } else { + unreachable!("Wrong function harness template.") + } + + // Arguments for internal function call + let args = &mut fn_call.args; + let default_borrowed_field = args.pop().unwrap().into_value(); + let default_field = args.pop().unwrap().into_value(); + + for item in (*signature).inputs.iter() { + match item { + FnArg::Typed(i) => { + if let Pat::Ident(id) = &*i.pat { + match *i.ty.clone() { + Type::Reference(rf) => { + let mut new_field = default_borrowed_field.clone(); + if let Expr::Reference(ref mut new_rf) = new_field { + // Copying borrow mutability + new_rf.mutability = rf.mutability; + // Copying field ident + if let Expr::Unary(ref mut new_subfield) = *new_rf.expr { + if let Expr::Field(ref mut new_unary_subfield) = + *new_subfield.expr { - if let Expr::Field(ref mut new_unary_subfield) = - *new_subfield.expr - { - new_unary_subfield.member = - Member::Named(id.ident.clone()); - } else { - panic!("Wrong borrowed field template"); - } + new_unary_subfield.member = + Member::Named(id.ident.clone()); } else { - panic!("Wrong borrowed field template"); + unreachable!("Wrong borrowed field template"); } } else { - panic!("Wrong borrowed field template"); + unreachable!("Wrong borrowed field template"); } - - // Pushing arguments to the function call - args.push(new_field); - } - Type::Path(_) => { - let mut new_field = default_field.clone(); - if let Expr::Field(ref mut f) = new_field { - f.member = Member::Named(id.ident.clone()); - } else { - panic!("Wrong unborrowed field template"); - } - // Pushing arguments to the function call - args.push(new_field); + } else { + unreachable!("Wrong borrowed field template"); } - _ => { - return Err(GenerateError::ComplexArg); + + // Pushing arguments to the function call + args.push(new_field); + } + Type::Path(_) => { + let mut new_field = default_field.clone(); + if let Expr::Field(ref mut f) = new_field { + f.member = Member::Named(id.ident.clone()); + } else { + unreachable!("Wrong unborrowed field template"); } - }; - } else { - return Err(GenerateError::ComplexVariable); - } - } - FnArg::Receiver(_) => { - panic!( - "This macros can not be used for fuzzing methods, use #[create_cargofuzz_impl_harness]" - ); + // Pushing arguments to the function call + args.push(new_field); + } + _ => { + return Err(Error::ComplexArg); + } + }; + } else { + return Err(Error::ComplexVariable); } } + FnArg::Receiver(_) => { + panic!( + "This macros can not be used for fuzzing methods, use #[create_cargofuzz_impl_harness]" + ); + } } - } else { - panic!("Wrong function call template."); } + } else { + unreachable!("Wrong function call template."); } } @@ -456,7 +451,7 @@ pub fn fuzz_function( &(*signature).ident.to_string() ) } else { - return Err(GenerateError::ComplexSelfType); + return Err(Error::ComplexSelfType); } } None => { @@ -476,7 +471,7 @@ pub fn fuzz_function( &(*signature).ident.to_string() ) } else { - return Err(GenerateError::ComplexSelfType); + return Err(Error::ComplexSelfType); } } None => { @@ -503,7 +498,7 @@ pub fn fuzz_harness( &(*signature).ident.to_string() ) } else { - panic!("Complex self type.") + unimplemented!("Complex self type.") } } None => { @@ -520,7 +515,7 @@ pub fn fuzz_harness( &(*signature).ident.to_string() ) } else { - panic!("Complex self type.") + unimplemented!("Complex self type.") } } None => { @@ -529,10 +524,10 @@ pub fn fuzz_harness( }; let path = { - if !attr.is_empty() { - quote!(#crate_ident :: #attr ::) - } else { + if attr.is_empty() { quote!(#crate_ident ::) + } else { + quote!(#crate_ident :: #attr ::) } }; @@ -551,7 +546,7 @@ pub fn fuzz_harness( } #[derive(Debug, PartialEq)] -pub enum GenerateError { +pub enum Error { Unsafe, Async, Empty, @@ -562,17 +557,17 @@ pub enum GenerateError { ComplexVariable, } -impl fmt::Display for GenerateError { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let err_msg = match self { - GenerateError::Async => "Can not fuzz async functions.", - GenerateError::Unsafe => "unsafe functions can not be fuzzed automatically.", - GenerateError::Empty => "It is useless to fuzz function without input parameters.", - GenerateError::ComplexArg => "Type of the function must be either standalone, or borrowed standalone (like `&Type`, but not like `&(u32, String)`)", - GenerateError::ComplexSelfType => "Only implementations for simple (like `MyType`) types are supported", - GenerateError::MultipleRes => "Muptiple Self values in function args.", - GenerateError::ComplexMethodCall => "Complex method calls are not currently supported.", - GenerateError::ComplexVariable => "Complex variables (like `&mut *a`) are not supported", + Error::Async => "Can not fuzz async functions.", + Error::Unsafe => "unsafe functions can not be fuzzed automatically.", + Error::Empty => "It is useless to fuzz function without input parameters.", + Error::ComplexArg => "Type of the function must be either standalone, or borrowed standalone (like `&Type`, but not like `&(u32, String)`)", + Error::ComplexSelfType => "Only implementations for simple (like `MyType`) types are supported", + Error::MultipleRes => "Muptiple Self values in function args.", + Error::ComplexMethodCall => "Complex method calls are not currently supported.", + Error::ComplexVariable => "Complex variables (like `&mut *a`) are not supported", }; write!(f, "{}", err_msg) @@ -709,10 +704,7 @@ mod tests { } }) .unwrap(); - assert_eq!( - fuzz_struct(&function.sig, None), - Err(GenerateError::ComplexArg) - ); + assert_eq!(fuzz_struct(&function.sig, None), Err(Error::ComplexArg)); } #[test] @@ -729,7 +721,7 @@ mod tests { .unwrap(); assert_eq!( fuzz_struct(&function.sig, None), - Err(GenerateError::ComplexVariable) + Err(Error::ComplexVariable) ); } @@ -814,7 +806,7 @@ mod tests { .unwrap(); assert_eq!( fuzz_function(&function.sig, None), - Err(GenerateError::ComplexVariable) + Err(Error::ComplexVariable) ); } @@ -830,10 +822,7 @@ mod tests { } }) .unwrap(); - assert_eq!( - fuzz_function(&function.sig, None), - Err(GenerateError::Empty) - ); + assert_eq!(fuzz_function(&function.sig, None), Err(Error::Empty)); } #[test] diff --git a/src/lib.rs b/src/lib.rs index e485c8d..587ed86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::{quote, format_ident}; +use quote::{format_ident, quote}; use std::env; use std::fs; use syn::{ImplItem, ItemFn, ItemImpl, ItemStruct, Type}; @@ -43,11 +43,7 @@ fn create_function_harness(attr: TokenStream, input: proc_macro::TokenStream) -> let crate_ident = format_ident!("{}", &crate_name_underscored); // Writing fuzzing harness to file - let ident = if attr.is_empty() { - function.sig.ident.to_string() - } else { - attr.to_string().replace("::", "__") + "__" + &function.sig.ident.to_string() - }; + let ident = crate_parse::construct_harness_ident(&function.sig.ident, None, &attr); let code = generate::fuzz_harness(&function.sig, None, &crate_ident, &attr); @@ -59,7 +55,7 @@ fn create_function_harness(attr: TokenStream, input: proc_macro::TokenStream) -> // TODO: Error handing crate_info - .write_cargo_toml(&function.sig.ident, None, &attr) + .add_target_to_cargo_toml(&function.sig.ident, None, &attr) .expect("Failed to update Cargo.toml"); quote!( @@ -95,7 +91,7 @@ fn create_impl_harness(attr: TokenStream, input: proc_macro::TokenStream) -> Tok let mut fuzz_structs = Vec::::new(); let mut fuzz_functions = Vec::::new(); - for item in implementation.items.iter() { + for item in &implementation.items { if let ImplItem::Method(method) = item { let fuzz_struct_result = generate::fuzz_struct(&method.sig, Some(&implementation.self_ty)); @@ -105,22 +101,32 @@ fn create_impl_harness(attr: TokenStream, input: proc_macro::TokenStream) -> Tok match (fuzz_struct_result, fuzz_function_result) { (Ok(fuzz_struct), Ok(fuzz_function)) => { // Writing fuzzing harness to file - let code = generate::fuzz_harness(&method.sig, Some(&implementation.self_ty), &crate_ident, &attr); + let code = generate::fuzz_harness( + &method.sig, + Some(&implementation.self_ty), + &crate_ident, + &attr, + ); let filename = if let Type::Path(ref path) = *implementation.self_ty { - format!("{}_{}.rs", &(path.path.segments.iter().next().unwrap().ident).to_string(), &method.sig.ident.to_string()) + format!( + "{}_{}.rs", + &(path.path.segments.iter().next().unwrap().ident).to_string(), + &method.sig.ident.to_string() + ) } else { panic!("Complex self type.") }; - fs::write( - fuzz_dir_path.join(filename), - code.to_string(), - ) - .expect("Failed to write fuzzing harness to fuzz/fuzz_targets"); + fs::write(fuzz_dir_path.join(filename), code.to_string()) + .expect("Failed to write fuzzing harness to fuzz/fuzz_targets"); // TODO: Error handing crate_info - .write_cargo_toml(&method.sig.ident,Some(&implementation.self_ty), &attr) + .add_target_to_cargo_toml( + &method.sig.ident, + Some(&implementation.self_ty), + &attr, + ) .expect("Failed to update Cargo.toml"); fuzz_structs.push(fuzz_struct); fuzz_functions.push(fuzz_function); @@ -141,8 +147,6 @@ fn create_impl_harness(attr: TokenStream, input: proc_macro::TokenStream) -> Tok continue; } } - } else { - continue; } } diff --git a/test-lib/Cargo.lock b/test-lib/Cargo.lock index d52f29d..100c2d1 100644 --- a/test-lib/Cargo.lock +++ b/test-lib/Cargo.lock @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "auto-fuzz-test" -version = "0.2.3" +version = "0.2.4" dependencies = [ "cargo_toml", "fs3",