Skip to content

Commit

Permalink
🧑‍💻 tidy up trdelnik code, as a lot of stuff was implemented on multi…
Browse files Browse the repository at this point in the history
…ple places and multiple times, add program stubs kind of automatic generenerating logic and fuzzer with token example
  • Loading branch information
lukacan authored and lukacan committed Nov 24, 2023
1 parent 2cb87b1 commit 7c406e5
Show file tree
Hide file tree
Showing 35 changed files with 1,844 additions and 2,022 deletions.
26 changes: 19 additions & 7 deletions crates/cli/src/command/build.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
use anyhow::Error;
use anyhow::{bail, Error};
use fehler::throws;
use trdelnik_client::*;

use crate::discover;

pub const TRDELNIK_TOML: &str = "Trdelnik.toml";

#[throws]
pub async fn build(root: String) {
let commander = Commander::with_root(root);
commander.create_program_client_crate().await?;
commander.build_programs().await?;
commander.generate_program_client_deps().await?;
commander.generate_program_client_lib_rs().await?;
pub async fn build(root: Option<String>) {
let root = match root {
Some(r) => r,
_ => {
let root = discover(TRDELNIK_TOML)?;
if let Some(r) = root {
r
} else {
bail!("It does not seem that Trdelnik is initialized because the Trdelnik.toml file was not found in any parent directory!");
}
}
};
let mut builder = WorkspaceBuilder::new_with_root(root);
builder.build().await?;
}
16 changes: 12 additions & 4 deletions crates/cli/src/command/clean.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use anyhow::Error;
use anyhow::{bail, Error};
use fehler::throws;
use trdelnik_client::Cleaner;
use trdelnik_client::WorkspaceBuilder;

use crate::discover;
pub const TRDELNIK_TOML: &str = "Trdelnik.toml";

#[throws]
pub async fn clean() {
let cleaner = Cleaner::new();
cleaner.clean_target().await?;
let root = if let Some(r) = discover(TRDELNIK_TOML)? {
r
} else {
bail!("It does not seem that Trdelnik is initialized because the Trdelnik.toml file was not found in any parent directory!");
};
let builder = WorkspaceBuilder::new_with_root(root);
builder.clean().await?;
}
296 changes: 11 additions & 285 deletions crates/cli/src/command/fuzz.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use std::{
env,
path::{Path, PathBuf},
process,
};
use std::path::Path;

use anyhow::{bail, Context, Error, Result};
use anyhow::{bail, Error};

use clap::Subcommand;
use fehler::throws;
use trdelnik_client::Commander;

pub const TESTS_WORKSPACE: &str = "trdelnik-tests";
pub const HFUZZ_WORKSPACE: &str = "hfuzz_workspace";
use crate::discover;

pub const TRDELNIK_TOML: &str = "Trdelnik.toml";

#[derive(Subcommand)]
#[allow(non_camel_case_types)]
Expand All @@ -38,7 +35,7 @@ pub async fn fuzz(root: Option<String>, subcmd: FuzzCommand) {
let root = match root {
Some(r) => r,
_ => {
let root = _discover()?;
let root = discover(TRDELNIK_TOML)?;
if let Some(r) = root {
r
} else {
Expand All @@ -47,296 +44,25 @@ pub async fn fuzz(root: Option<String>, subcmd: FuzzCommand) {
}
};

let commander = Commander::with_root(root.clone());
//let commander = Commander::with_root(root.clone());
let root_path = Path::new(&root);

match subcmd {
FuzzCommand::Run {
target,
with_exit_code,
} => {
if with_exit_code {
// Parse the HFUZZ run arguments to find out if the crash folder and crash files extension was modified.
let hfuzz_run_args = env::var("HFUZZ_RUN_ARGS").unwrap_or_default();
let (crash_dir, ext) = get_crash_dir_and_ext(&root, &target, &hfuzz_run_args);

if let Ok(crash_files) = get_crash_files(&crash_dir, &ext) {
if !crash_files.is_empty() {
println!("Error: The crash directory {} already contains crash files from previous runs. \n\nTo run Trdelnik fuzzer with exit code, you must either (backup and) remove the old crash files or alternatively change the crash folder using for example the --crashdir option and the HFUZZ_RUN_ARGS env variable such as:\nHFUZZ_RUN_ARGS=\"--crashdir ./new_crash_dir\"", crash_dir.to_string_lossy());
process::exit(1);
}
}
commander.run_fuzzer(target).await?;
if let Ok(crash_files) = get_crash_files(&crash_dir, &ext) {
if !crash_files.is_empty() {
println!(
"The crash directory {} contains new fuzz test crashes. Exiting!",
crash_dir.to_string_lossy()
);
process::exit(1);
}
}
Commander::run_fuzzer_with_exit_code(target, root_path).await?;
} else {
commander.run_fuzzer(target).await?;
Commander::run_fuzzer(target, root_path).await?;
}
}
FuzzCommand::Run_Debug {
target,
crash_file_path,
} => {
commander.run_fuzzer_debug(target, crash_file_path).await?;
Commander::run_fuzzer_debug(target, crash_file_path, root_path).await?;
}
};
}

// Climbs each parent directory until we find Trdelnik.toml.
fn _discover() -> Result<Option<String>> {
let _cwd = std::env::current_dir()?;
let mut cwd_opt = Some(_cwd.as_path());

while let Some(cwd) = cwd_opt {
for f in std::fs::read_dir(cwd)
.with_context(|| format!("Error reading the directory with path: {}", cwd.display()))?
{
let p = f
.with_context(|| {
format!("Error reading the directory with path: {}", cwd.display())
})?
.path();
if let Some(filename) = p.file_name() {
if filename.to_str() == Some("Trdelnik.toml") {
return Ok(Some(cwd.to_string_lossy().to_string()));
}
}
}

cwd_opt = cwd.parent();
}

Ok(None)
}

fn get_crash_dir_and_ext(root: &str, target: &str, hfuzz_run_args: &str) -> (PathBuf, String) {
// FIXME: we split by whitespace without respecting escaping or quotes - same approach as honggfuzz-rs so there is no point to fix it here before the upstream is fixed
let hfuzz_run_args = hfuzz_run_args.split_whitespace();

let extension =
get_cmd_option_value(hfuzz_run_args.clone(), "-e", "--ext").unwrap_or("fuzz".to_string());

let crash_dir = get_cmd_option_value(hfuzz_run_args.clone(), "", "--cr")
.or_else(|| get_cmd_option_value(hfuzz_run_args.clone(), "-W", "--w"));

let crash_path = if let Some(dir) = crash_dir {
Path::new(root).join(TESTS_WORKSPACE).join(dir)
} else {
Path::new(root)
.join(TESTS_WORKSPACE)
.join(HFUZZ_WORKSPACE)
.join(target)
};

(crash_path, extension)
}

fn get_cmd_option_value<'a>(
hfuzz_run_args: impl Iterator<Item = &'a str>,
short_opt: &str,
long_opt: &str,
) -> Option<String> {
let mut args_iter = hfuzz_run_args;
let mut value: Option<String> = None;

// ensure short option starts with one dash and long option with two dashes
let short_opt = format!("-{}", short_opt.trim_start_matches('-'));
let long_opt = format!("--{}", long_opt.trim_start_matches('-'));

while let Some(arg) = args_iter.next() {
match arg.strip_prefix(&short_opt) {
Some(val) if short_opt.len() > 1 => {
if !val.is_empty() {
// -ecrash for crash extension with no space
value = Some(val.to_string());
} else if let Some(next_arg) = args_iter.next() {
// -e crash for crash extension with space
value = Some(next_arg.to_string());
} else {
value = None;
}
}
_ => {
if arg.starts_with(&long_opt) && long_opt.len() > 2 {
value = args_iter.next().map(|a| a.to_string());
}
}
}
}

value
}

fn get_crash_files(
dir: &PathBuf,
extension: &str,
) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
let paths = std::fs::read_dir(dir)?
// Filter out all those directory entries which couldn't be read
.filter_map(|res| res.ok())
// Map the directory entries to paths
.map(|dir_entry| dir_entry.path())
// Filter out all paths with extensions other than `extension`
.filter_map(|path| {
if path.extension().map_or(false, |ext| ext == extension) {
Some(path)
} else {
None
}
})
.collect::<Vec<_>>();
Ok(paths)
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cmd_options_parsing() {
let mut command = String::from("-Q -v --extension fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q --extension fuzz -v");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -e fuzz -v");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q --extension fuzz -v --extension ");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

command = String::from("-Q --extension fuzz -v -e ");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

let mut command = String::from("--extension buzz -e fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v -e fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v -efuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v --ext fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, Some("fuzz".to_string()));

command = String::from("-Q -v --extfuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

command = String::from("-Q -v --workspace");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "--ext");
assert_eq!(extension, None);

command = String::from("-Q -v -e");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "", "--ext");
assert_eq!(extension, None);

command = String::from("-Q -v --ext fuzz");
let args = command.split_whitespace();

let extension = get_cmd_option_value(args, "-e", "");
assert_eq!(extension, None);
}

#[test]
fn test_get_crash_dir_and_ext() {
let root = "/home/fuzz";
let target = "target";
let default_crash_path = Path::new(root)
.join(TESTS_WORKSPACE)
.join(HFUZZ_WORKSPACE)
.join(target);

let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "");

assert_eq!(crash_dir, default_crash_path);
assert_eq!(&ext, "fuzz");

let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -e");

assert_eq!(crash_dir, default_crash_path);
assert_eq!(&ext, "fuzz");

let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -e crash");

assert_eq!(crash_dir, default_crash_path);
assert_eq!(&ext, "crash");

// test absolute path
let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -W /home/crash -e crash");

let expected_crash_path = Path::new("/home/crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// test absolute path
let (crash_dir, ext) =
get_crash_dir_and_ext(root, target, "-Q --crash /home/crash -e crash");

let expected_crash_path = Path::new("/home/crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// test relative path
let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q -W ../crash -e crash");

let expected_crash_path = Path::new(root).join(TESTS_WORKSPACE).join("../crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// test relative path
let (crash_dir, ext) = get_crash_dir_and_ext(root, target, "-Q --crash ../crash -e crash");

let expected_crash_path = Path::new(root).join(TESTS_WORKSPACE).join("../crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");

// crash directory has precedence before workspace
let (crash_dir, ext) =
get_crash_dir_and_ext(root, target, "-Q --crash ../crash -W /workspace -e crash");

let expected_crash_path = Path::new(root).join(TESTS_WORKSPACE).join("../crash");
assert_eq!(crash_dir, expected_crash_path);
assert_eq!(&ext, "crash");
}
}
Loading

0 comments on commit 7c406e5

Please sign in to comment.