1//! A library to generate __fuzzed__ C headers for use with `quickcheck`
2//!
3//! ## Example
4//!
5//! ```rust
6//! extern crate quickcheck;
7//! extern crate quickchecking;
8//! extern crate rand;
9//! use quickcheck::{Arbitrary, Gen, StdGen};
10//! use quickchecking::fuzzers;
11//! use rand::thread_rng;
12//!
13//! fn main() {
14//!     let generate_range: usize = 10; // Determines things like the length of
15//!                                     // arbitrary vectors generated.
16//!     let header = fuzzers::HeaderC::arbitrary(
17//!        &mut StdGen::new(thread_rng(), generate_range));
18//!     println!("{}", header);
19//! }
20//! ```
21//!
22#![deny(missing_docs)]
23#[macro_use]
24extern crate lazy_static;
25extern crate quickcheck;
26extern crate rand;
27extern crate tempdir;
28
29use quickcheck::{QuickCheck, StdGen, TestResult};
30use rand::thread_rng;
31use std::error::Error;
32use std::fs::File;
33use std::io::Write;
34use std::path::PathBuf;
35use std::process::{Command, Output};
36use std::sync::Mutex;
37use tempdir::TempDir;
38
39/// Contains definitions of and impls for types used to fuzz C declarations.
40pub mod fuzzers;
41
42// Global singleton, manages context across tests. For now that context is
43// only the output_path for inspecting fuzzed headers (if specified).
44struct Context {
45    output_path: Option<String>,
46}
47
48// Initialize global context.
49lazy_static! {
50    static ref CONTEXT: Mutex<Context> =
51        Mutex::new(Context { output_path: None });
52}
53
54// Passes fuzzed header to the `csmith-fuzzing/predicate.py` script, returns
55// output of the associated command.
56fn run_predicate_script(
57    header: fuzzers::HeaderC,
58) -> Result<Output, Box<dyn Error>> {
59    let dir = TempDir::new("bindgen_prop")?;
60    let header_path = dir.path().join("prop_test.h");
61
62    let mut header_file = File::create(&header_path)?;
63    header_file.write_all(header.to_string().as_bytes())?;
64    header_file.sync_all()?;
65
66    let header_path_string = header_path
67        .into_os_string()
68        .into_string()
69        .map_err(|_| "error converting path into String")?;
70
71    let mut predicate_script_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
72    predicate_script_path.push("../../csmith-fuzzing/predicate.py");
73
74    let predicate_script_path_string = predicate_script_path
75        .into_os_string()
76        .into_string()
77        .map_err(|_| "error converting path into String")?;
78
79    // Copy generated temp files to output_path directory for inspection.
80    // If `None`, output path not specified, don't copy.
81    if let Some(ref path) = CONTEXT.lock().unwrap().output_path {
82        Command::new("cp")
83            .arg("-a")
84            .arg(dir.path().to_str().unwrap())
85            .arg(path)
86            .output()?;
87    }
88
89    Ok(Command::new(&predicate_script_path_string)
90        .arg(&header_path_string)
91        .output()?)
92}
93
94// Generatable property. Pass generated headers off to run through the
95// `csmith-fuzzing/predicate.py` script. Success is measured by the success
96// status of that command.
97fn bindgen_prop(header: fuzzers::HeaderC) -> TestResult {
98    match run_predicate_script(header) {
99        Ok(o) => TestResult::from_bool(o.status.success()),
100        Err(e) => {
101            println!("{:?}", e);
102            TestResult::from_bool(false)
103        }
104    }
105}
106
107/// Instantiate a Quickcheck object and use it to run property tests using
108/// fuzzed C headers generated with types defined in the `fuzzers` module.
109/// Success/Failure is dictated by the result of passing the fuzzed headers
110/// to the `csmith-fuzzing/predicate.py` script.
111pub fn test_bindgen(
112    generate_range: usize,
113    tests: usize,
114    output_path: Option<&str>,
115) {
116    if let Some(path) = output_path {
117        CONTEXT.lock().unwrap().output_path =
118            Some(String::from(PathBuf::from(path).to_str().unwrap()));
119    }
120
121    QuickCheck::new()
122        .tests(tests)
123        .gen(StdGen::new(thread_rng(), generate_range))
124        .quickcheck(bindgen_prop as fn(fuzzers::HeaderC) -> TestResult)
125}
126