diff --git a/cargo-klee/src/cli.rs b/cargo-klee/src/cli.rs index 6052692ef795c003b776bf12a3a031d5491f18b9..b6e9b7f8b40e1980bc8728fccb8cdcddef9d8407 100644 --- a/cargo-klee/src/cli.rs +++ b/cargo-klee/src/cli.rs @@ -9,27 +9,39 @@ pub struct Cli { command: String, /// Binary to build #[arg(long, group = "binary")] - bin: Option<String>, + pub bin: Option<String>, /// Example to build #[arg(long, group = "binary")] - example: Option<String>, + pub example: Option<String>, - /// Arguments to rustc as a string (e.g., "--release, --target", etc) - #[arg(long, allow_hyphen_values = true)] - rustc: Option<String>, + /// Build artifacts in release mode, with optimizations + #[arg(long)] + pub release: bool, + + /// Enable features, e.g, --features "foo bar" + #[arg(long)] + pub features: Option<String>, + + /// Enable all-features + #[arg(long)] + pub all_features: bool, - /// Verbose output from rustc + /// Verbose output #[arg(long)] - verbose: bool, + pub verbose: bool, + + /// Additional arguments to rustc as a string (e.g., "--target", etc) + #[arg(long, allow_hyphen_values = true)] + pub rustc: Option<String>, /// Klee invocation #[arg(long, short)] - klee: bool, + pub klee: bool, /// GDB Replay #[arg(long, short)] - replay: bool, + pub replay: bool, // #[command(subcommand)] // binary: Option<Commands>, } @@ -42,27 +54,57 @@ fn cli_verify() { #[test] fn cli_parse_simple() { - let args = Cli::try_parse_from(&["test"]).unwrap(); + let args = Cli::try_parse_from(&["test", "klee"]).unwrap(); assert_eq!(args.bin, None); + assert_eq!(args.all_features, false); } #[test] fn cli_parse_bin() { - let args = Cli::try_parse_from(&["test", "klee", "--bin", "a"]).unwrap(); + let args = Cli::try_parse_from(&["test", "klee", "--bin", "foo"]).unwrap(); println!("args {:?}", args); - assert_eq!(args.bin, Some("a".to_owned())); + assert_eq!(&args.bin.unwrap(), "foo"); } #[test] fn cli_parse_example() { - let args = Cli::try_parse_from(&["test", "klee", "--example", "b"]).unwrap(); + let args = Cli::try_parse_from(&["test", "klee", "--example", "bar"]).unwrap(); println!("args {:?}", args); - assert_eq!(args.example, Some("b".to_owned())); + assert_eq!(&args.example.unwrap(), "bar"); } #[test] fn cli_parse_bin_example() { - let args = Cli::try_parse_from(&["test", "klee", "--bin", "a", "--example", "b"]); + let args = Cli::try_parse_from(&["test", "klee", "--bin", "foo", "--example", "bar"]); println!("args {:?}", args); assert_eq!(args.is_err(), true); } + +#[test] +fn cli_parse_bin_release() { + let args = Cli::try_parse_from(&["test", "klee", "--release"]).unwrap(); + println!("args {:?}", args); + assert_eq!(args.release, true); +} + +#[test] +fn cli_parse_bin_features() { + let args = Cli::try_parse_from(&["test", "klee", "--features", "foo bar"]).unwrap(); + println!("args {:?}", args); + assert_eq!(args.features, Some("foo bar".to_owned())); +} + +#[test] +fn cli_parse_bin_all_features() { + let args = Cli::try_parse_from(&["test", "klee", "--all-features"]).unwrap(); + println!("args {:?}", args); + assert_eq!(args.all_features, true); +} + +#[test] +fn cli_parse_bin_rustc() { + let args = Cli::try_parse_from(&["test", "klee", "--rustc", "--target=wasm32-unknown-unknown"]) + .unwrap(); + println!("args {:?}", args); + assert_eq!(&args.rustc.unwrap(), "--target=wasm32-unknown-unknown"); +} diff --git a/cargo-klee/src/main.rs b/cargo-klee/src/main.rs index 852abb011b5adb9847870fcb956693dc1240f665..416a1b310a3ddc538d5f9749e614600e92133c2e 100644 --- a/cargo-klee/src/main.rs +++ b/cargo-klee/src/main.rs @@ -6,14 +6,14 @@ use clap::Parser; // extern crate libc; -// use std::{ -// env, fs, -// path::PathBuf, -// process::{self, Command}, -// time::SystemTime, -// }; +use std::{ + env, fs, + path::PathBuf, + process::{self, Command}, + time::SystemTime, +}; -// use cargo_project::{Artifact, Profile, Project}; +use cargo_project::{Artifact, Profile, Project}; // #[derive(Debug, Parser)] // #[command(author, version, about, long_about = None)] @@ -40,27 +40,106 @@ use clap::Parser; // } fn main() -> Result<(), Error> { - let args = Cli::parse(); + let args = Cli::try_parse()?; + + let cwd = env::current_dir()?; + + let meta = rustc_version::version_meta()?; + let host = meta.host; + + let project = Project::query(cwd)?; println!("{:?}", args); + + // we rely on `clap` for either `example` or `bin` + let file = if let Some(ref example) = args.example { + example + } else if let Some(ref bin) = args.bin { + bin + } else { + // if neither --bin nor --example is provided we + // use the Cargo project name + project.name() + }; + println!("file {}", file); + + // turn `cargo klee --example foo` into `cargo rustc --example foo -- (..)` + let mut cargo = Command::new("cargo"); + cargo + // compile using rustc + .arg("rustc"); + + // verbose output for debugging purposes + if args.verbose { + cargo.arg("-v"); + } + + // set features, always including `klee-analysis` + if args.all_features { + cargo.arg("--all-features"); + } else { + if let Some(features) = args.features { + let mut vec: Vec<&str> = features.split(" ").collect(); + vec.push("klee-analysis"); + cargo.args(&["--features", &vec.join(" ")]); + } else { + cargo.args(&["--features", "klee-analysis"]); + } + } + + if args.release { + cargo.arg("--release"); + } + // propagate additional arguments to rustc, e.g. --target=wasm32-unknown-unknown + // TODO! this is not tested + if let Some(rustc) = args.rustc { + let vec: Vec<&str> = rustc.split(" ").collect(); + for v in vec { + cargo.arg(v); + } + } + + // select (single) application to compile + if args.example.is_some() { + cargo.args(&["--example", &file]); + } else { + // file is either the explicitly stated binary or the crate name + cargo.args(&["--bin", &file]); + } + + // prepare for klee build + cargo + // enable shell coloring of result + .arg("--color=always") + .arg("--") + // ignore linking + .args(&["-C", "linker=true"]) + // force LTO, to get a single oject file + .args(&["-C", "lto"]) + // output the LLVM-IR (.ll file) for KLEE analysis + .arg("--emit=llvm-ir") + // force panic=abort in all crates, override .cargo settings + .env("RUSTFLAGS", "-C panic=abort"); + // TODO, force `incremental=false`, `codegen-units=1`? + + println!("cargo {:?}", cargo); + + if args.verbose { + eprintln!("\n{:?}\n", cargo); + } + + // execute the command and unwrap the result into status + let status = cargo.status()?; + + if !status.success() { + // TODO! correctly handle exit status + panic!("{:?}", status); + // return Err(status.code().unwrap_or(1).into()); + } + Ok(()) } -// fn main() -> Result<(), Box<dyn std::error::Error>> { -// let args = Cli::parse(); -// println!("args {:?}", args); -// Ok(()) -// } -// fn main() -> Result<(), failure::Error> { -// match run() { -// Ok(ec) => process::exit(ec), -// Err(e) => { -// eprintln!("error: {}", e); -// process::exit(1) -// } -// } -// } - // fn run() -> Result<i32, failure::Error> { // let matches = App::new("cargo-klee") // .version("0.4.0")