Skip to content

Commit

Permalink
♻️ Refactor feature
Browse files Browse the repository at this point in the history
  • Loading branch information
lukacan committed Mar 17, 2024
1 parent 62969c9 commit 278a2ae
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 233 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ incremented upon a breaking change and the patch version will be incremented for

## [Unreleased]
### Added
- feat/fuzzer-stats-logging, an optional stats output, triggered by `--run-with-stats`, during fuzzing sessions ([#144](https://github.com/Ackee-Blockchain/trident/pull/144))
- feat/fuzzer-stats-logging, an optional stats output, triggered by `--stats` or `-s`, during fuzzing sessions ([#144](https://github.com/Ackee-Blockchain/trident/pull/144))
- feat/support of automatically obtaining fully qualified paths of Data Accounts Custom types for `accounts_snapshots.rs` ([#141](https://github.com/Ackee-Blockchain/trident/pull/141))
- feat/allow direct accounts manipulation and storage ([#142](https://github.com/Ackee-Blockchain/trident/pull/142))
- feat/support of non-corresponding instruction and context names ([#130](https://github.com/Ackee-Blockchain/trident/pull/130))
Expand Down
12 changes: 5 additions & 7 deletions crates/cli/src/command/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ pub enum FuzzCommand {
/// Trident will return exit code 1 in case of found crash files in the crash folder. This is checked before and after the fuzz test run.
#[arg(short, long)]
with_exit_code: bool,
/// Trident will show statistics during fuzzing session. It is recommended to turn `keep_output` and `verbose` on true.
/// Trident will show statistics after the fuzzing session. It is recommended to turn `keep_output` on true.
#[arg(short, long)]
run_with_stats: bool,
stats: bool,
},
/// Debug fuzz target with crash file
Run_Debug {
Expand Down Expand Up @@ -53,14 +53,12 @@ pub async fn fuzz(root: Option<String>, subcmd: FuzzCommand) {
FuzzCommand::Run {
target,
with_exit_code,
run_with_stats,
stats,
} => {
if with_exit_code {
commander.run_fuzzer_with_exit_code(target).await?;
} else if run_with_stats {
commander.run_fuzzer_with_stats(target).await?;
commander.run_fuzzer_with_exit_code(target, stats).await?;
} else {
commander.run_fuzzer(target).await?;
commander.run_fuzzer(target, stats).await?;
}
}
FuzzCommand::Run_Debug {
Expand Down
1 change: 1 addition & 0 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ solana-banks-client = "<1.18"
indicatif = "0.17.8"
regex = "1.10.3"
solana-logger = "<1.18"
prettytable = "0.10.0"
31 changes: 15 additions & 16 deletions crates/client/derive/fuzz_test_executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ pub fn fuzz_test_executor(input: TokenStream) -> TokenStream {
let variant_name = &variant.ident;
quote! {
#enum_name::#variant_name (ix) => {
#[cfg(fuzzing)]
if client.allow_stats() {
client.accumulate_executed_ix(self.to_context_string());
}
#[cfg(fuzzing_with_stats)]
if let Some(fs) = fuzzing_stats{
fs.increase_invoked(self.to_context_string());
};
let (mut signers, metas) = ix.get_accounts(client, &mut accounts.borrow_mut())
.map_err(|e| e.with_origin(Origin::Instruction(self.to_context_string())))
.expect("Accounts calculation expect");
Expand Down Expand Up @@ -45,20 +45,20 @@ pub fn fuzz_test_executor(input: TokenStream) -> TokenStream {
.map_err(|e| e.with_origin(Origin::Instruction(self.to_context_string())));

if tx_res.is_ok() {
#[cfg(fuzzing)]
if client.allow_stats() {
client.accumulate_successful_ix(self.to_context_string());
}
#[cfg(fuzzing_with_stats)]
if let Some(fs) = fuzzing_stats{
fs.increase_successfully_invoked(self.to_context_string());
};
snaphot.capture_after(client).unwrap();
let (acc_before, acc_after) = snaphot.get_snapshot()
.map_err(|e| e.with_origin(Origin::Instruction(self.to_context_string())))
.expect("Snapshot deserialization expect"); // we want to panic if we cannot unwrap to cause a crash

if let Err(e) = ix.check(acc_before, acc_after, data).map_err(|e| e.with_origin(Origin::Instruction(self.to_context_string()))) {
#[cfg(fuzzing)]
if client.allow_stats() {
client.show_accumulated_stats();
}
#[cfg(fuzzing_with_stats)]
if let Some(fs) = fuzzing_stats{
fs.output_serialized();
};
eprintln!(
"CRASH DETECTED! Custom check after the {} instruction did not pass!",
self.to_context_string());
Expand All @@ -71,14 +71,13 @@ pub fn fuzz_test_executor(input: TokenStream) -> TokenStream {

quote! {
impl FuzzTestExecutor<FuzzAccounts> for FuzzInstruction {
fn run_fuzzer<C>(
fn run_fuzzer(
&self,
program_id: Pubkey,
accounts: &RefCell<FuzzAccounts>,
client: &mut C,
client: &mut impl FuzzClient,
fuzzing_stats: &mut Option<&mut FuzzingStatistics>,
) -> core::result::Result<(), Box<dyn std::error::Error + 'static>>
where
C: FuzzClient + StatsLogger,
{
match self {
#(#display_match_arms)*
Expand Down
159 changes: 100 additions & 59 deletions crates/client/src/commander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use tokio::{
};

use crate::constants::*;
use crate::fuzzing_stats::FuzzingStatistics;
use tokio::io::AsyncBufReadExt;

#[derive(Error, Debug)]
pub enum Error {
Expand Down Expand Up @@ -146,7 +148,7 @@ impl Commander {
}
/// Runs fuzzer on the given target with exit code option.
#[throws]
pub async fn run_fuzzer_with_exit_code(&self, target: String) {
pub async fn run_fuzzer_with_exit_code(&self, target: String, with_stats: bool) {
let config = Config::new();

// obtain hfuzz_run_args from env variable, this variable can contain multiple
Expand Down Expand Up @@ -181,26 +183,31 @@ impl Commander {
}
}

let mut child = Command::new("cargo")
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
.arg("hfuzz")
.arg("run")
.arg(target)
.spawn()?;

tokio::select! {
res = child.wait() =>
match res {
Ok(status) => if !status.success() {
println!("Honggfuzz exited with an error!");
},
Err(_) => throw!(Error::FuzzingFailed),
},
_ = signal::ctrl_c() => {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
},
match with_stats {
true => {
let mut child = Command::new("cargo")
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
.env("RUSTFLAGS", "--cfg fuzzing_with_stats")
.arg("hfuzz")
.arg("run")
.arg(target)
.stdout(Stdio::piped())
.spawn()?;
Self::handle_child_with_stats(&mut child).await?;
}
false => {
let mut child = Command::new("cargo")
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
.arg("hfuzz")
.arg("run")
.arg(target)
.spawn()?;
Self::handle_child(&mut child).await?;
}
}

if let Ok(crash_files) = get_crash_files(&crash_dir, &ext) {
Expand All @@ -216,7 +223,7 @@ impl Commander {

/// Runs fuzzer on the given target.
#[throws]
pub async fn run_fuzzer(&self, target: String) {
pub async fn run_fuzzer(&self, target: String, with_stats: bool) {
let config = Config::new();

let hfuzz_run_args = std::env::var("HFUZZ_RUN_ARGS").unwrap_or_default();
Expand All @@ -228,15 +235,45 @@ impl Commander {

let fuzz_args = config.get_fuzz_args(hfuzz_run_args);

let mut child = Command::new("cargo")
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
.arg("hfuzz")
.arg("run")
.arg(target)
.spawn()?;
match with_stats {
true => {
let mut child = Command::new("cargo")
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
.env("RUSTFLAGS", "--cfg fuzzing_with_stats")
.arg("hfuzz")
.arg("run")
.arg(target)
.stdout(Stdio::piped())
.spawn()?;
Self::handle_child_with_stats(&mut child).await?;
}
false => {
let mut child = Command::new("cargo")
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
.arg("hfuzz")
.arg("run")
.arg(target)
.spawn()?;
Self::handle_child(&mut child).await?;
}
}
}

/// Manages a child process in an async context, specifically for monitoring fuzzing tasks.
/// Waits for the process to exit or a Ctrl+C signal. Prints an error message if the process
/// exits with an error, and sleeps briefly on Ctrl+C. Throws `Error::FuzzingFailed` on errors.
///
/// # Arguments
/// * `child` - A mutable reference to a `Child` process.
///
/// # Errors
/// * Throws `Error::FuzzingFailed` if waiting on the child process fails.
#[throws]
async fn handle_child(child: &mut Child) {
tokio::select! {
res = child.wait() =>
match res {
Expand All @@ -250,46 +287,50 @@ impl Commander {
},
}
}

/// Runs fuzzer on the given target and turn FUZZING_STATS env variable on true
/// Based on the variable, fuzzer will show statistics during fuzzing session
/// Asynchronously manages a child process for fuzzing tasks, collecting and logging statistics.
/// It waits for the process to exit or for a Ctrl+C signal, logging output statistics in either case.
/// On process exit, it checks the exit status and prints an error message if the process exited with an error.
/// On Ctrl+C, it also sleeps briefly. In both cases, it captures and logs the stdout of the child process.
/// Finally, it displays the collected statistics in a table format.
/// Throws `Error::FuzzingFailed` if waiting on the child process or reading its output fails.
///
/// # Arguments
/// * `child` - A mutable reference to a `Child` process.
///
/// # Errors
/// * Throws `Error::FuzzingFailed` if there's an issue with the child process.
#[throws]
pub async fn run_fuzzer_with_stats(&self, target: String) {
let config = Config::new();

let hfuzz_run_args = std::env::var("HFUZZ_RUN_ARGS").unwrap_or_default();

// std::env::set_var("FUZZING_STATS", "true");

let cargo_target_dir = std::env::var("CARGO_TARGET_DIR")
.unwrap_or_else(|_| config.get_env_arg("CARGO_TARGET_DIR"));
let hfuzz_workspace = std::env::var("HFUZZ_WORKSPACE")
.unwrap_or_else(|_| config.get_env_arg("HFUZZ_WORKSPACE"));

let fuzz_args = config.get_fuzz_args(hfuzz_run_args);

let mut child = Command::new("cargo")
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
.env("FUZZING_STATS", "true")
.arg("hfuzz")
.arg("run")
.arg(target)
.spawn()?;
async fn handle_child_with_stats(child: &mut Child) {
let mut stats_logger = FuzzingStatistics::new();

tokio::select! {
res = child.wait() =>
res = child.wait() =>{

let stdout = child.stdout.take().expect("child did not have a handle to stdout");
let mut reader = tokio::io::BufReader::new(stdout).lines();
while let Some(line) = reader.next_line().await? {
stats_logger.insert_serialized(&line);
}
match res {
Ok(status) => if !status.success() {
println!("Honggfuzz exited with an error!");
Ok(status) => {
if !status.success() {
println!("Honggfuzz exited with an error!");
}
},
Err(_) => throw!(Error::FuzzingFailed),
}
},
_ = signal::ctrl_c() => {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

let stdout = child.stdout.take().expect("child did not have a handle to stdout");
let mut reader = tokio::io::BufReader::new(stdout).lines();
while let Some(line) = reader.next_line().await? {
stats_logger.insert_serialized(&line);
}
},
}
stats_logger.show_table();
}

/// Runs fuzzer on the given target.
Expand Down
26 changes: 0 additions & 26 deletions crates/client/src/fuzzer/client_stats.rs

This file was deleted.

Loading

0 comments on commit 278a2ae

Please sign in to comment.