112a9d9c8Sopenharmony_ci//! A library to generate __fuzzed__ C headers for use with `quickcheck`
212a9d9c8Sopenharmony_ci//!
312a9d9c8Sopenharmony_ci//! ## Example
412a9d9c8Sopenharmony_ci//!
512a9d9c8Sopenharmony_ci//! ```rust
612a9d9c8Sopenharmony_ci//! extern crate quickcheck;
712a9d9c8Sopenharmony_ci//! extern crate quickchecking;
812a9d9c8Sopenharmony_ci//! extern crate rand;
912a9d9c8Sopenharmony_ci//! use quickcheck::{Arbitrary, Gen, StdGen};
1012a9d9c8Sopenharmony_ci//! use quickchecking::fuzzers;
1112a9d9c8Sopenharmony_ci//! use rand::thread_rng;
1212a9d9c8Sopenharmony_ci//!
1312a9d9c8Sopenharmony_ci//! fn main() {
1412a9d9c8Sopenharmony_ci//!     let generate_range: usize = 10; // Determines things like the length of
1512a9d9c8Sopenharmony_ci//!                                     // arbitrary vectors generated.
1612a9d9c8Sopenharmony_ci//!     let header = fuzzers::HeaderC::arbitrary(
1712a9d9c8Sopenharmony_ci//!        &mut StdGen::new(thread_rng(), generate_range));
1812a9d9c8Sopenharmony_ci//!     println!("{}", header);
1912a9d9c8Sopenharmony_ci//! }
2012a9d9c8Sopenharmony_ci//! ```
2112a9d9c8Sopenharmony_ci//!
2212a9d9c8Sopenharmony_ci#![deny(missing_docs)]
2312a9d9c8Sopenharmony_ci#[macro_use]
2412a9d9c8Sopenharmony_ciextern crate lazy_static;
2512a9d9c8Sopenharmony_ciextern crate quickcheck;
2612a9d9c8Sopenharmony_ciextern crate rand;
2712a9d9c8Sopenharmony_ciextern crate tempdir;
2812a9d9c8Sopenharmony_ci
2912a9d9c8Sopenharmony_ciuse quickcheck::{QuickCheck, StdGen, TestResult};
3012a9d9c8Sopenharmony_ciuse rand::thread_rng;
3112a9d9c8Sopenharmony_ciuse std::error::Error;
3212a9d9c8Sopenharmony_ciuse std::fs::File;
3312a9d9c8Sopenharmony_ciuse std::io::Write;
3412a9d9c8Sopenharmony_ciuse std::path::PathBuf;
3512a9d9c8Sopenharmony_ciuse std::process::{Command, Output};
3612a9d9c8Sopenharmony_ciuse std::sync::Mutex;
3712a9d9c8Sopenharmony_ciuse tempdir::TempDir;
3812a9d9c8Sopenharmony_ci
3912a9d9c8Sopenharmony_ci/// Contains definitions of and impls for types used to fuzz C declarations.
4012a9d9c8Sopenharmony_cipub mod fuzzers;
4112a9d9c8Sopenharmony_ci
4212a9d9c8Sopenharmony_ci// Global singleton, manages context across tests. For now that context is
4312a9d9c8Sopenharmony_ci// only the output_path for inspecting fuzzed headers (if specified).
4412a9d9c8Sopenharmony_cistruct Context {
4512a9d9c8Sopenharmony_ci    output_path: Option<String>,
4612a9d9c8Sopenharmony_ci}
4712a9d9c8Sopenharmony_ci
4812a9d9c8Sopenharmony_ci// Initialize global context.
4912a9d9c8Sopenharmony_cilazy_static! {
5012a9d9c8Sopenharmony_ci    static ref CONTEXT: Mutex<Context> =
5112a9d9c8Sopenharmony_ci        Mutex::new(Context { output_path: None });
5212a9d9c8Sopenharmony_ci}
5312a9d9c8Sopenharmony_ci
5412a9d9c8Sopenharmony_ci// Passes fuzzed header to the `csmith-fuzzing/predicate.py` script, returns
5512a9d9c8Sopenharmony_ci// output of the associated command.
5612a9d9c8Sopenharmony_cifn run_predicate_script(
5712a9d9c8Sopenharmony_ci    header: fuzzers::HeaderC,
5812a9d9c8Sopenharmony_ci) -> Result<Output, Box<dyn Error>> {
5912a9d9c8Sopenharmony_ci    let dir = TempDir::new("bindgen_prop")?;
6012a9d9c8Sopenharmony_ci    let header_path = dir.path().join("prop_test.h");
6112a9d9c8Sopenharmony_ci
6212a9d9c8Sopenharmony_ci    let mut header_file = File::create(&header_path)?;
6312a9d9c8Sopenharmony_ci    header_file.write_all(header.to_string().as_bytes())?;
6412a9d9c8Sopenharmony_ci    header_file.sync_all()?;
6512a9d9c8Sopenharmony_ci
6612a9d9c8Sopenharmony_ci    let header_path_string = header_path
6712a9d9c8Sopenharmony_ci        .into_os_string()
6812a9d9c8Sopenharmony_ci        .into_string()
6912a9d9c8Sopenharmony_ci        .map_err(|_| "error converting path into String")?;
7012a9d9c8Sopenharmony_ci
7112a9d9c8Sopenharmony_ci    let mut predicate_script_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
7212a9d9c8Sopenharmony_ci    predicate_script_path.push("../../csmith-fuzzing/predicate.py");
7312a9d9c8Sopenharmony_ci
7412a9d9c8Sopenharmony_ci    let predicate_script_path_string = predicate_script_path
7512a9d9c8Sopenharmony_ci        .into_os_string()
7612a9d9c8Sopenharmony_ci        .into_string()
7712a9d9c8Sopenharmony_ci        .map_err(|_| "error converting path into String")?;
7812a9d9c8Sopenharmony_ci
7912a9d9c8Sopenharmony_ci    // Copy generated temp files to output_path directory for inspection.
8012a9d9c8Sopenharmony_ci    // If `None`, output path not specified, don't copy.
8112a9d9c8Sopenharmony_ci    if let Some(ref path) = CONTEXT.lock().unwrap().output_path {
8212a9d9c8Sopenharmony_ci        Command::new("cp")
8312a9d9c8Sopenharmony_ci            .arg("-a")
8412a9d9c8Sopenharmony_ci            .arg(dir.path().to_str().unwrap())
8512a9d9c8Sopenharmony_ci            .arg(path)
8612a9d9c8Sopenharmony_ci            .output()?;
8712a9d9c8Sopenharmony_ci    }
8812a9d9c8Sopenharmony_ci
8912a9d9c8Sopenharmony_ci    Ok(Command::new(&predicate_script_path_string)
9012a9d9c8Sopenharmony_ci        .arg(&header_path_string)
9112a9d9c8Sopenharmony_ci        .output()?)
9212a9d9c8Sopenharmony_ci}
9312a9d9c8Sopenharmony_ci
9412a9d9c8Sopenharmony_ci// Generatable property. Pass generated headers off to run through the
9512a9d9c8Sopenharmony_ci// `csmith-fuzzing/predicate.py` script. Success is measured by the success
9612a9d9c8Sopenharmony_ci// status of that command.
9712a9d9c8Sopenharmony_cifn bindgen_prop(header: fuzzers::HeaderC) -> TestResult {
9812a9d9c8Sopenharmony_ci    match run_predicate_script(header) {
9912a9d9c8Sopenharmony_ci        Ok(o) => TestResult::from_bool(o.status.success()),
10012a9d9c8Sopenharmony_ci        Err(e) => {
10112a9d9c8Sopenharmony_ci            println!("{:?}", e);
10212a9d9c8Sopenharmony_ci            TestResult::from_bool(false)
10312a9d9c8Sopenharmony_ci        }
10412a9d9c8Sopenharmony_ci    }
10512a9d9c8Sopenharmony_ci}
10612a9d9c8Sopenharmony_ci
10712a9d9c8Sopenharmony_ci/// Instantiate a Quickcheck object and use it to run property tests using
10812a9d9c8Sopenharmony_ci/// fuzzed C headers generated with types defined in the `fuzzers` module.
10912a9d9c8Sopenharmony_ci/// Success/Failure is dictated by the result of passing the fuzzed headers
11012a9d9c8Sopenharmony_ci/// to the `csmith-fuzzing/predicate.py` script.
11112a9d9c8Sopenharmony_cipub fn test_bindgen(
11212a9d9c8Sopenharmony_ci    generate_range: usize,
11312a9d9c8Sopenharmony_ci    tests: usize,
11412a9d9c8Sopenharmony_ci    output_path: Option<&str>,
11512a9d9c8Sopenharmony_ci) {
11612a9d9c8Sopenharmony_ci    if let Some(path) = output_path {
11712a9d9c8Sopenharmony_ci        CONTEXT.lock().unwrap().output_path =
11812a9d9c8Sopenharmony_ci            Some(String::from(PathBuf::from(path).to_str().unwrap()));
11912a9d9c8Sopenharmony_ci    }
12012a9d9c8Sopenharmony_ci
12112a9d9c8Sopenharmony_ci    QuickCheck::new()
12212a9d9c8Sopenharmony_ci        .tests(tests)
12312a9d9c8Sopenharmony_ci        .gen(StdGen::new(thread_rng(), generate_range))
12412a9d9c8Sopenharmony_ci        .quickcheck(bindgen_prop as fn(fuzzers::HeaderC) -> TestResult)
12512a9d9c8Sopenharmony_ci}
126