diff options
Diffstat (limited to 'bindgen-tests/tests/quickchecking/src')
-rw-r--r-- | bindgen-tests/tests/quickchecking/src/bin.rs | 112 | ||||
-rw-r--r-- | bindgen-tests/tests/quickchecking/src/fuzzers.rs | 635 | ||||
-rw-r--r-- | bindgen-tests/tests/quickchecking/src/lib.rs | 133 |
3 files changed, 880 insertions, 0 deletions
diff --git a/bindgen-tests/tests/quickchecking/src/bin.rs b/bindgen-tests/tests/quickchecking/src/bin.rs new file mode 100644 index 00000000..f2b52e82 --- /dev/null +++ b/bindgen-tests/tests/quickchecking/src/bin.rs @@ -0,0 +1,112 @@ +//! An application to run property tests for `bindgen` with _fuzzed_ C headers +//! using `quickcheck` +//! +//! ## Usage +//! +//! Print help +//! ```bash +//! $ cargo run --bin=quickchecking -- -h +//! ``` +//! +//! Run with default values +//! ```bash +//! $ cargo run --bin=quickchecking +//! ``` +//! +#![deny(missing_docs)] +extern crate clap; +extern crate quickchecking; + +use clap::{App, Arg}; +use std::path::Path; + +// Validate CLI argument input for generation range. +fn validate_generate_range(v: String) -> Result<(), String> { + match v.parse::<usize>() { + Ok(_) => Ok(()), + Err(_) => Err(String::from( + "Generate range could not be converted to a usize.", + )), + } +} + +// Validate CLI argument input for tests count. +fn validate_tests_count(v: String) -> Result<(), String> { + match v.parse::<usize>() { + Ok(_) => Ok(()), + Err(_) => Err(String::from( + "Tests count could not be converted to a usize.", + )), + } +} + +// Validate CLI argument input for fuzzed headers output path. +fn validate_path(v: String) -> Result<(), String> { + match Path::new(&v).is_dir() { + true => Ok(()), + false => Err(String::from("Provided directory path does not exist.")), + } +} + +fn main() { + let matches = App::new("quickchecking") + .version("0.2.0") + .about( + "Bindgen property tests with quickcheck. \ + Generate random valid C code and pass it to the \ + csmith/predicate.py script", + ) + .arg( + Arg::with_name("path") + .short("p") + .long("path") + .value_name("PATH") + .help( + "Optional. Preserve generated headers for inspection, \ + provide directory path for header output. [default: None] ", + ) + .takes_value(true) + .validator(validate_path), + ) + .arg( + Arg::with_name("range") + .short("r") + .long("range") + .value_name("RANGE") + .help( + "Sets the range quickcheck uses during generation. \ + Corresponds to things like arbitrary usize and \ + arbitrary vector length. This number doesn't have \ + to grow much for execution time to increase \ + significantly.", + ) + .takes_value(true) + .default_value("32") + .validator(validate_generate_range), + ) + .arg( + Arg::with_name("count") + .short("c") + .long("count") + .value_name("COUNT") + .help( + "Count / number of tests to run. Running a fuzzed \ + header through the predicate.py script can take a \ + long time, especially if the generation range is \ + large. Increase this number if you're willing to \ + wait a while.", + ) + .takes_value(true) + .default_value("2") + .validator(validate_tests_count), + ) + .get_matches(); + + let output_path: Option<&str> = matches.value_of("path"); + let generate_range: usize = + matches.value_of("range").unwrap().parse::<usize>().unwrap(); + let tests: usize = + matches.value_of("count").unwrap().parse::<usize>().unwrap(); + + quickchecking::test_bindgen(generate_range, tests, output_path) +} diff --git a/bindgen-tests/tests/quickchecking/src/fuzzers.rs b/bindgen-tests/tests/quickchecking/src/fuzzers.rs new file mode 100644 index 00000000..b609dd5a --- /dev/null +++ b/bindgen-tests/tests/quickchecking/src/fuzzers.rs @@ -0,0 +1,635 @@ +use quickcheck::{Arbitrary, Gen, StdGen}; +use rand::thread_rng; +use std::fmt; + +/// BaseTypeC is used in generation of C headers to represent the C language's +/// primitive types as well as `void*`. +#[derive(Debug, Clone)] +pub struct BaseTypeC { + /// String representation of C type. + pub def: String, +} + +/// TypeQualifierC is used in generation of C headers to represent qualifiers +/// such as `const`. +#[derive(Debug, Clone)] +pub struct TypeQualifierC { + /// String representation of C type qualifier. + pub def: String, +} + +/// PointerLevelC is used in generation of C headers to represent number of +/// `*` for pointer types. +#[derive(Debug, Clone)] +pub struct PointerLevelC { + /// String representation of C declaration's pointer level. + pub def: String, +} + +/// ArrayDimensionC is used in generation of C headers to represent number of +/// `[]` used to define array types. +#[derive(Debug, Clone)] +pub struct ArrayDimensionC { + /// String representation of C declaration's array dimension. + pub def: String, +} + +/// BasicTypeDeclarationC is used in generation of C headers to represent +/// declarations outside of function pointers that take the form +/// `BaseTypeC` + `TypeQualifierC` + `PointerLevelC` + `ident_id`. +#[derive(Debug, Clone)] +pub struct BasicTypeDeclarationC { + /// The declaration's base type, i.e. `int`. + pub type_name: BaseTypeC, + /// The declaration's type qualifier, i.e. `const`. + pub type_qualifier: TypeQualifierC, + /// The declaration's pointer level, i.e. `***`. + pub pointer_level: PointerLevelC, + /// The declaration's array dimension, i.e. [][][]. + pub array_dimension: ArrayDimensionC, + /// The declaration's identifier, i.e. ident_N. + pub ident_id: String, +} + +/// StructDeclarationC is used in generation of C headers to represent the +/// definition of a struct type. +#[derive(Debug, Clone)] +pub struct StructDeclarationC { + /// The declaration's fields. + pub fields: DeclarationListC, + /// The declaration's array dimension, i.e. [][][]. + pub array_dimension: ArrayDimensionC, + /// The declaration's identifier, i.e. struct_N. + pub ident_id: String, +} + +/// UnionDeclarationC is used in generation of C headers to represent the +/// definition of a union type. +#[derive(Debug, Clone)] +pub struct UnionDeclarationC { + /// The declaration's fields. + pub fields: DeclarationListC, + /// The declaration's array dimension, i.e. [][][]. + pub array_dimension: ArrayDimensionC, + /// The declaration's identifier, i.e. union_N. + pub ident_id: String, +} + +/// FunctionPointerDeclarationC is used in generation of C headers to represent +/// the definition of a function pointer type. +#[derive(Debug, Clone)] +pub struct FunctionPointerDeclarationC { + /// The function's type qualifier, i.e. `const`. + pub type_qualifier: TypeQualifierC, + /// The function's return type, i.e. `int`. + pub type_name: BaseTypeC, + /// The function's pointer level, i.e. `***`. + pub pointer_level: PointerLevelC, + /// The function's parameters. + pub params: ParameterListC, + /// The declaration's identifier, i.e. func_ptr_N. + pub ident_id: String, +} + +/// FunctionPrototypeC is used in generation of C headers to represent the +/// definition of a function prototype. +#[derive(Debug, Clone)] +pub struct FunctionPrototypeC { + /// The function's type qualifier, i.e. `const`. + pub type_qualifier: TypeQualifierC, + /// The function's return type, i.e. `int`. + pub type_name: BaseTypeC, + /// The function's pointer level, i.e. `***`. + pub pointer_level: PointerLevelC, + /// The function's parameters. + pub params: ParameterListC, + /// The prototype's identifier, i.e. `func_N`. + pub ident_id: String, +} + +/// ParameterC is used in generation of C headers to represent the +/// definition function parameters. +#[derive(Debug, Clone)] +pub struct ParameterC { + /// The parameter's type qualifier, i.e. `const`. + pub type_qualifier: TypeQualifierC, + /// The parameter's base type, i.e. `int`. + pub type_name: BaseTypeC, + /// The parameter's pointer level, i.e. `***`. + pub pointer_level: PointerLevelC, +} + +/// ParameterListC is used in generation of C headers to represent a list of +/// definitions of function parameters. +#[derive(Debug, Clone)] +pub struct ParameterListC { + /// Parameters that define a C function signature. + pub params: Vec<ParameterC>, +} + +/// DeclarationC is used in generation of C headers to represent all supported +/// C type declarations allowed in the generated header. +#[derive(Debug, Clone)] +pub enum DeclarationC { + /// Function prototype declaration kind. + FunctionDecl(FunctionPrototypeC), + /// Function pointer declaration kind. + FunctionPtrDecl(FunctionPointerDeclarationC), + /// Struct declaration kind. + StructDecl(StructDeclarationC), + /// Union declaration kind. + UnionDecl(UnionDeclarationC), + /// Basic type declaration kind. + VariableDecl(BasicTypeDeclarationC), +} + +/// DeclarationListC is used in generation of C headers to represent a list of +/// declarations. +#[derive(Debug, Clone)] +pub struct DeclarationListC { + /// Grouping of C declarations. + pub decls: Vec<DeclarationC>, +} + +/// HeaderC is used in generation of C headers to represent a collection of +/// declarations. +#[derive(Clone)] +pub struct HeaderC { + /// The header's declarations. + pub def: DeclarationListC, +} + +/// MakeUnique is used in generation of C headers to make declaration +/// identifiers unique by incorporating the `stamp` parameter into it's name. +trait MakeUnique { + fn make_unique(&mut self, stamp: usize); +} + +/// MakeUnique is used in generation of C headers to make DeclarationC +/// identifiers unique. +impl MakeUnique for DeclarationC { + fn make_unique(&mut self, stamp: usize) { + match *self { + DeclarationC::FunctionDecl(ref mut d) => d.make_unique(stamp), + DeclarationC::FunctionPtrDecl(ref mut d) => d.make_unique(stamp), + DeclarationC::StructDecl(ref mut d) => d.make_unique(stamp), + DeclarationC::UnionDecl(ref mut d) => d.make_unique(stamp), + DeclarationC::VariableDecl(ref mut d) => d.make_unique(stamp), + } + } +} + +/// A qucickcheck trait for describing how DeclarationC types can be +/// randomly generated and shrunk. +impl Arbitrary for DeclarationC { + fn arbitrary<G: Gen>(g: &mut G) -> DeclarationC { + match g.gen_range(0, 5) { + 0 => DeclarationC::FunctionDecl(FunctionPrototypeC::arbitrary(g)), + 1 => DeclarationC::FunctionPtrDecl( + FunctionPointerDeclarationC::arbitrary(g), + ), + 2 => DeclarationC::StructDecl(StructDeclarationC::arbitrary(g)), + 3 => DeclarationC::UnionDecl(UnionDeclarationC::arbitrary(g)), + 4 => { + DeclarationC::VariableDecl(BasicTypeDeclarationC::arbitrary(g)) + } + _ => unreachable!(), + } + } +} + +/// Enables to string and format for DeclarationC types. +impl fmt::Display for DeclarationC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + DeclarationC::FunctionPtrDecl(ref d) => write!(f, "{}", d), + DeclarationC::StructDecl(ref d) => write!(f, "{}", d), + DeclarationC::UnionDecl(ref d) => write!(f, "{}", d), + DeclarationC::VariableDecl(ref d) => write!(f, "{}", d), + DeclarationC::FunctionDecl(ref d) => write!(f, "{}", d), + } + } +} + +/// A qucickcheck trait for describing how DeclarationListC types can be +/// randomly generated and shrunk. +impl Arbitrary for DeclarationListC { + fn arbitrary<G: Gen>(g: &mut G) -> DeclarationListC { + DeclarationListC { + decls: Arbitrary::arbitrary(g), + } + } +} + +/// Enables to string and format for DeclarationListC types. +impl fmt::Display for DeclarationListC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut display = String::new(); + for decl in &self.decls { + display += &format!("{}", decl); + } + write!(f, "{}", display) + } +} + +/// A qucickcheck trait for describing how BaseTypeC types can be +/// randomly generated and shrunk. +impl Arbitrary for BaseTypeC { + fn arbitrary<G: Gen>(g: &mut G) -> BaseTypeC { + // Special case `long double` until issue #550 is resolved. + let base_type = vec![ + "char", + "signed char", + "unsigned char", + "short", + "short int", + "signed short", + "signed short int", + "unsigned short", + "unsigned short int", + "int", + "signed", + "signed int", + "unsigned", + "unsigned int", + "long", + "long int", + "signed long", + "signed long int", + "unsigned long", + "unsigned long int", + "long long", + "long long int", + "signed long long", + "signed long long int", + "unsigned long long", + "unsigned long long int", + "float", + "double", + #[cfg(feature = "long-doubles")] + "long double", + "void*", + ]; + BaseTypeC { + def: String::from(*g.choose(&base_type).unwrap()), + } + } +} + +/// Enables to string and format for BaseTypeC types, +impl fmt::Display for BaseTypeC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.def) + } +} + +/// A qucickcheck trait for describing how TypeQualifierC types can be +/// randomly generated and shrunk. +impl Arbitrary for TypeQualifierC { + fn arbitrary<G: Gen>(g: &mut G) -> TypeQualifierC { + let qualifier = vec!["const", ""]; + TypeQualifierC { + def: String::from(*g.choose(&qualifier).unwrap()), + } + } +} + +/// Enables to string and format for TypeQualifierC types. +impl fmt::Display for TypeQualifierC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.def) + } +} + +/// A qucickcheck trait for describing how PointerLevelC types can be +/// randomly generated and shrunk. +impl Arbitrary for PointerLevelC { + fn arbitrary<G: Gen>(g: &mut G) -> PointerLevelC { + PointerLevelC { + // 16 is an arbitrary "not too big" number for capping pointer level. + def: (0..g.gen_range(0, 16)).map(|_| "*").collect::<String>(), + } + } +} + +/// Enables to string and format for PointerLevelC types. +impl fmt::Display for PointerLevelC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.def) + } +} + +/// A qucickcheck trait for describing how ArrayDimensionC types can be +/// randomly generated and shrunk. +impl Arbitrary for ArrayDimensionC { + fn arbitrary<G: Gen>(g: &mut G) -> ArrayDimensionC { + // Keep these small, clang complains when they get too big. + let dimensions = g.gen_range(0, 5); + let mut def = String::new(); + + let lower_bound; + if cfg!(feature = "zero-sized-arrays") { + lower_bound = 0; + } else { + lower_bound = 1; + } + + for _ in 1..dimensions { + // 16 is an arbitrary "not too big" number for capping array size. + def += &format!("[{}]", g.gen_range(lower_bound, 16)); + } + ArrayDimensionC { def } + } +} + +/// Enables to string and format for ArrayDimensionC types. +impl fmt::Display for ArrayDimensionC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.def) + } +} + +/// MakeUnique is used in generation of C headers to make BasicTypeDeclarationC +/// identifiers unique. +impl MakeUnique for BasicTypeDeclarationC { + fn make_unique(&mut self, stamp: usize) { + self.ident_id += &format!("_{}", stamp); + } +} + +/// A qucickcheck trait for describing how BasicTypeDeclarationC types can be +/// randomly generated and shrunk. +impl Arbitrary for BasicTypeDeclarationC { + fn arbitrary<G: Gen>(g: &mut G) -> BasicTypeDeclarationC { + BasicTypeDeclarationC { + type_qualifier: Arbitrary::arbitrary(g), + type_name: Arbitrary::arbitrary(g), + pointer_level: Arbitrary::arbitrary(g), + array_dimension: Arbitrary::arbitrary(g), + ident_id: format!("{}", usize::arbitrary(g)), + } + } +} + +/// Enables to string and format for BasicTypeDeclarationC types. +impl fmt::Display for BasicTypeDeclarationC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {} {} ident_{}{};", + self.type_qualifier, + self.type_name, + self.pointer_level, + self.ident_id, + self.array_dimension + ) + } +} + +/// MakeUnique is used in generation of C headers to make StructDeclarationC +/// identifiers unique. +impl MakeUnique for StructDeclarationC { + fn make_unique(&mut self, stamp: usize) { + self.ident_id += &format!("_{}", stamp); + } +} + +/// A qucickcheck trait for describing how StructDeclarationC types can be +/// randomly generated and shrunk. +impl Arbitrary for StructDeclarationC { + fn arbitrary<G: Gen>(g: &mut G) -> StructDeclarationC { + // Reduce generator size as a method of putting a bound on recursion. + // When size < 1 the empty list is generated. + let reduced_size: usize = (g.size() / 2) as usize + 1; + let mut decl_list: DeclarationListC = + Arbitrary::arbitrary(&mut StdGen::new(thread_rng(), reduced_size)); + let mut fields: DeclarationListC = DeclarationListC { decls: vec![] }; + + for (i, decl) in decl_list.decls.iter_mut().enumerate() { + match *decl { + DeclarationC::FunctionDecl(_) => {} + ref mut decl => { + decl.make_unique(i); + fields.decls.push(decl.clone()); + } + } + } + + StructDeclarationC { + fields, + ident_id: format!("{}", usize::arbitrary(g)), + array_dimension: Arbitrary::arbitrary(g), + } + } +} + +/// Enables to string and format for StructDeclarationC types. +impl fmt::Display for StructDeclarationC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "struct {{ {} }} struct_{}{};", + self.fields, self.ident_id, self.array_dimension + ) + } +} + +/// MakeUnique is used in generation of C headers to make UnionDeclarationC +/// identifiers unique. +impl MakeUnique for UnionDeclarationC { + fn make_unique(&mut self, stamp: usize) { + self.ident_id += &format!("_{}", stamp); + } +} + +/// A qucickcheck trait for describing how UnionDeclarationC types can be +/// randomly generated and shrunk. +impl Arbitrary for UnionDeclarationC { + fn arbitrary<G: Gen>(g: &mut G) -> UnionDeclarationC { + // Reduce generator size as a method of putting a bound on recursion. + // When size < 1 the empty list is generated. + let reduced_size: usize = (g.size() / 2) as usize + 1; + let mut decl_list: DeclarationListC = + Arbitrary::arbitrary(&mut StdGen::new(thread_rng(), reduced_size)); + let mut fields: DeclarationListC = DeclarationListC { decls: vec![] }; + + for (i, decl) in decl_list.decls.iter_mut().enumerate() { + match *decl { + DeclarationC::FunctionDecl(_) => {} + ref mut decl => { + decl.make_unique(i); + fields.decls.push(decl.clone()); + } + } + } + + UnionDeclarationC { + fields, + ident_id: format!("{}", usize::arbitrary(g)), + array_dimension: Arbitrary::arbitrary(g), + } + } +} + +/// Enables to string and format for UnionDeclarationC types. +impl fmt::Display for UnionDeclarationC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "union {{ {} }} union_{}{};", + self.fields, self.ident_id, self.array_dimension + ) + } +} + +/// MakeUnique is used in generation of C headers to make +/// FunctionPointerDeclarationC identifiers unique. +impl MakeUnique for FunctionPointerDeclarationC { + fn make_unique(&mut self, stamp: usize) { + self.ident_id += &format!("_{}", stamp); + } +} + +/// A qucickcheck trait for describing how FunctionPointerDeclarationC types can +/// be randomly generated and shrunk. +impl Arbitrary for FunctionPointerDeclarationC { + fn arbitrary<G: Gen>(g: &mut G) -> FunctionPointerDeclarationC { + FunctionPointerDeclarationC { + type_qualifier: Arbitrary::arbitrary(g), + type_name: Arbitrary::arbitrary(g), + pointer_level: Arbitrary::arbitrary(g), + params: Arbitrary::arbitrary(g), + ident_id: format!("{}", usize::arbitrary(g)), + } + } +} + +/// Enables to string and format for FunctionPointerDeclarationC types. +impl fmt::Display for FunctionPointerDeclarationC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {} {} (*func_ptr_{})({});", + self.type_qualifier, + self.type_name, + self.pointer_level, + self.ident_id, + self.params + ) + } +} + +/// MakeUnique is used in generation of C headers to make FunctionPrototypeC +/// identifiers unique. +impl MakeUnique for FunctionPrototypeC { + fn make_unique(&mut self, stamp: usize) { + self.ident_id += &format!("_{}", stamp); + } +} + +/// A qucickcheck trait for describing how FunctionPrototypeC types can be +/// randomly generated and shrunk. +impl Arbitrary for FunctionPrototypeC { + fn arbitrary<G: Gen>(g: &mut G) -> FunctionPrototypeC { + FunctionPrototypeC { + type_qualifier: Arbitrary::arbitrary(g), + type_name: Arbitrary::arbitrary(g), + pointer_level: Arbitrary::arbitrary(g), + params: Arbitrary::arbitrary(g), + ident_id: format!("{}", usize::arbitrary(g)), + } + } +} + +/// Enables to string and format for FunctionPrototypeC types. +impl fmt::Display for FunctionPrototypeC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {} {} func_{}({});", + self.type_qualifier, + self.type_name, + self.pointer_level, + self.ident_id, + self.params + ) + } +} + +/// A qucickcheck trait for describing how ParameterC types can be +/// randomly generated and shrunk. +impl Arbitrary for ParameterC { + fn arbitrary<G: Gen>(g: &mut G) -> ParameterC { + ParameterC { + type_qualifier: Arbitrary::arbitrary(g), + type_name: Arbitrary::arbitrary(g), + pointer_level: Arbitrary::arbitrary(g), + } + } +} + +/// Enables to string and format for ParameterC types. +impl fmt::Display for ParameterC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {} {}", + self.type_qualifier, self.type_name, self.pointer_level + ) + } +} + +/// A qucickcheck trait for describing how ParameterListC types can be +/// randomly generated and shrunk. +impl Arbitrary for ParameterListC { + fn arbitrary<G: Gen>(g: &mut G) -> ParameterListC { + ParameterListC { + params: Arbitrary::arbitrary(g), + } + } +} + +/// Enables to string and format for ParameterListC types. +impl fmt::Display for ParameterListC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut display = String::new(); + for (i, p) in self.params.iter().enumerate() { + match i { + 0 => display += &format!("{}", p), + _ => display += &format!(",{}", p), + } + } + write!(f, "{}", display) + } +} + +/// A qucickcheck trait for describing how HeaderC types can be +/// randomly generated and shrunk. +impl Arbitrary for HeaderC { + fn arbitrary<G: Gen>(g: &mut G) -> HeaderC { + let mut decl_list: DeclarationListC = Arbitrary::arbitrary(g); + for (i, decl) in decl_list.decls.iter_mut().enumerate() { + decl.make_unique(i); + } + HeaderC { def: decl_list } + } +} + +/// Enables to string and format for HeaderC types. +impl fmt::Display for HeaderC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut display = String::new(); + for decl in &self.def.decls { + display += &format!("{}", decl); + } + write!(f, "{}", display) + } +} + +/// Use Display trait for Debug so that any failing property tests report +/// generated C code rather than the data structures that contain it. +impl fmt::Debug for HeaderC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} diff --git a/bindgen-tests/tests/quickchecking/src/lib.rs b/bindgen-tests/tests/quickchecking/src/lib.rs new file mode 100644 index 00000000..b09d1c49 --- /dev/null +++ b/bindgen-tests/tests/quickchecking/src/lib.rs @@ -0,0 +1,133 @@ +//! A library to generate __fuzzed__ C headers for use with `quickcheck` +//! +//! ## Example +//! +//! ```rust +//! extern crate quickcheck; +//! extern crate quickchecking; +//! extern crate rand; +//! use quickcheck::{Arbitrary, Gen, StdGen}; +//! use quickchecking::fuzzers; +//! use rand::thread_rng; +//! +//! fn main() { +//! let generate_range: usize = 10; // Determines things like the length of +//! // arbitrary vectors generated. +//! let header = fuzzers::HeaderC::arbitrary( +//! &mut StdGen::new(thread_rng(), generate_range)); +//! println!("{}", header); +//! } +//! ``` +//! +#![deny(missing_docs)] +#[macro_use] +extern crate lazy_static; +extern crate quickcheck; +extern crate rand; +extern crate tempdir; + +use quickcheck::{QuickCheck, StdGen, TestResult}; +use rand::thread_rng; +use std::error::Error; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; +use std::process::{Command, Output}; +use std::sync::Mutex; +use tempdir::TempDir; + +/// Contains definitions of and impls for types used to fuzz C declarations. +pub mod fuzzers; + +// Global singleton, manages context across tests. For now that context is +// only the output_path for inspecting fuzzed headers (if specified). +struct Context { + output_path: Option<String>, +} + +// Initialize global context. +lazy_static! { + static ref CONTEXT: Mutex<Context> = + Mutex::new(Context { output_path: None }); +} + +// Passes fuzzed header to the `csmith-fuzzing/predicate.py` script, returns +// output of the associated command. +fn run_predicate_script( + header: fuzzers::HeaderC, +) -> Result<Output, Box<dyn Error>> { + let dir = TempDir::new("bindgen_prop")?; + let header_path = dir.path().join("prop_test.h"); + + let mut header_file = File::create(&header_path)?; + header_file.write_all(header.to_string().as_bytes())?; + header_file.sync_all()?; + + let header_path_string; + match header_path.into_os_string().into_string() { + Ok(s) => header_path_string = s, + Err(_) => return Err(From::from("error converting path into String")), + } + + let mut predicate_script_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + predicate_script_path.push("../../csmith-fuzzing/predicate.py"); + + let predicate_script_path_string; + match predicate_script_path.into_os_string().into_string() { + Ok(s) => predicate_script_path_string = s, + Err(_) => return Err(From::from("error converting path into String")), + } + + // Copy generated temp files to output_path directory for inspection. + // If `None`, output path not specified, don't copy. + match CONTEXT.lock().unwrap().output_path { + Some(ref path) => { + Command::new("cp") + .arg("-a") + .arg(&dir.path().to_str().unwrap()) + .arg(&path) + .output()?; + } + None => {} + } + + Ok(Command::new(&predicate_script_path_string) + .arg(&header_path_string) + .output()?) +} + +// Generatable property. Pass generated headers off to run through the +// `csmith-fuzzing/predicate.py` script. Success is measured by the success +// status of that command. +fn bindgen_prop(header: fuzzers::HeaderC) -> TestResult { + match run_predicate_script(header) { + Ok(o) => return TestResult::from_bool(o.status.success()), + Err(e) => { + println!("{:?}", e); + return TestResult::from_bool(false); + } + } +} + +/// Instantiate a Quickcheck object and use it to run property tests using +/// fuzzed C headers generated with types defined in the `fuzzers` module. +/// Success/Failure is dictated by the result of passing the fuzzed headers +/// to the `csmith-fuzzing/predicate.py` script. +pub fn test_bindgen( + generate_range: usize, + tests: usize, + output_path: Option<&str>, +) { + match output_path { + Some(path) => { + CONTEXT.lock().unwrap().output_path = + Some(String::from(PathBuf::from(path).to_str().unwrap())); + } + None => {} // Path not specified, don't provide output. + } + + QuickCheck::new() + .tests(tests) + .gen(StdGen::new(thread_rng(), generate_range)) + .quickcheck(bindgen_prop as fn(fuzzers::HeaderC) -> TestResult) +} |