2024-11-12 00:49:58 +01:00

359 lines
9.3 KiB
Rust

/*!
* # `LibAFL` fmt
*
* Formatting `LibAFL` since 2024
*/
#![forbid(unexpected_cfgs)]
#![allow(incomplete_features)]
#![warn(clippy::cargo)]
#![allow(ambiguous_glob_reexports)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(
clippy::unreadable_literal,
clippy::type_repetition_in_bounds,
clippy::missing_errors_doc,
clippy::cast_possible_truncation,
clippy::used_underscore_binding,
clippy::ptr_as_ptr,
clippy::missing_panics_doc,
clippy::missing_docs_in_private_items,
clippy::module_name_repetitions,
clippy::ptr_cast_constness,
clippy::unsafe_derive_deserialize,
clippy::similar_names,
clippy::too_many_lines
)]
#![cfg_attr(not(test), warn(
missing_debug_implementations,
missing_docs,
//trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
//unused_results
))]
#![cfg_attr(test, deny(
missing_debug_implementations,
missing_docs,
//trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_must_use,
//unused_results
))]
#![cfg_attr(
test,
deny(
bad_style,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)
)]
// Till they fix this buggy lint in clippy
#![allow(clippy::borrow_as_ptr)]
#![allow(clippy::borrow_deref_ref)]
use std::{
fs::read_to_string,
io,
io::ErrorKind,
path::{Path, PathBuf},
str::from_utf8,
};
use clap::Parser;
use colored::Colorize;
use regex::RegexSet;
use tokio::{process::Command, task::JoinSet};
use walkdir::{DirEntry, WalkDir};
use which::which;
const REF_LLVM_VERSION: u32 = 19;
fn is_workspace_toml(path: &Path) -> bool {
for line in read_to_string(path).unwrap().lines() {
if line.eq("[workspace]") {
return true;
}
}
false
}
async fn run_cargo_fmt(cargo_file_path: PathBuf, is_check: bool, verbose: bool) -> io::Result<()> {
// Make sure we parse the correct file
assert_eq!(
cargo_file_path.file_name().unwrap().to_str().unwrap(),
"Cargo.toml"
);
if is_workspace_toml(cargo_file_path.as_path()) {
println!("[*] Skipping {}...", cargo_file_path.as_path().display());
return Ok(());
}
let task_str = if is_check { "Checking" } else { "Formatting" };
let mut fmt_command = Command::new("cargo");
fmt_command
.arg("+nightly")
.arg("fmt")
.arg("--manifest-path")
.arg(cargo_file_path.as_path());
if is_check {
fmt_command.arg("--check");
}
if verbose {
println!(
"[*] {} {}...",
task_str,
cargo_file_path.as_path().display()
);
}
let res = fmt_command.output().await?;
if !res.status.success() {
let stdout = from_utf8(&res.stdout).unwrap();
let stderr = from_utf8(&res.stderr).unwrap();
return Err(io::Error::new(
ErrorKind::Other,
format!(
"Cargo fmt failed. Run cargo fmt for {cargo_file_path:#?}.\nstdout: {stdout}\nstderr: {stderr}\ncommand: {fmt_command:?}"),
));
}
Ok(())
}
async fn run_clang_fmt(
c_file_path: PathBuf,
clang: String,
is_check: bool,
verbose: bool,
) -> io::Result<()> {
let task_str = if is_check { "Checking" } else { "Formatting" };
let mut fmt_command = Command::new(&clang);
fmt_command
.arg("-i")
.arg("--style")
.arg("file")
.arg(c_file_path.as_path());
if is_check {
fmt_command.arg("-Werror").arg("--dry-run");
}
fmt_command.arg(c_file_path.as_path());
if verbose {
println!("[*] {} {}...", task_str, c_file_path.as_path().display());
}
let res = fmt_command.output().await?;
if res.status.success() {
Ok(())
} else {
let stdout = from_utf8(&res.stdout).unwrap();
let stderr = from_utf8(&res.stderr).unwrap();
println!("{stderr}");
Err(io::Error::new(
ErrorKind::Other,
format!("{clang} failed.\nstdout:{stdout}\nstderr:{stderr}"),
))
}
}
#[derive(Parser)]
struct Cli {
#[arg(short, long)]
check: bool,
#[arg(short, long)]
verbose: bool,
}
#[tokio::main]
async fn main() -> io::Result<()> {
let cli = Cli::parse();
let libafl_root_dir = match project_root::get_project_root() {
Ok(p) => p,
Err(_) => std::env::current_dir().expect("Failed to get current directory"),
};
println!("Using {libafl_root_dir:#?} as the project root");
let rust_excluded_directories = RegexSet::new([
r".*target.*",
r".*utils/noaslr.*",
r".*utils/gdb_qemu.*",
r".*docs/listings/baby_fuzzer/listing-.*",
r".*LibAFL/Cargo.toml.*",
r".*AFLplusplus.*",
])
.expect("Could not create the regex set from the given regex");
let c_excluded_directories = RegexSet::new([
r".*target.*",
r".*libpng-1\.6.*",
r".*stb_image\.h$",
r".*dlmalloc\.c$",
r".*QEMU-Nyx.*",
r".*AFLplusplus.*",
r".*Little-CMS.*",
r".*cms_transform_fuzzer.cc.*",
r".*sqlite3.*",
])
.expect("Could not create the regex set from the given regex");
let c_file_to_format = RegexSet::new([
r".*\.cpp$",
r".*\.hpp$",
r".*\.cc$",
r".*\.cxx$",
r".*\.c$",
r".*\.h$",
])
.expect("Could not create the regex set from the given regex");
let rust_projects_to_fmt: Vec<PathBuf> = WalkDir::new(&libafl_root_dir)
.into_iter()
.filter_map(Result::ok)
.filter(|e| !rust_excluded_directories.is_match(e.path().as_os_str().to_str().unwrap()))
.filter(|e| e.file_name() == "Cargo.toml")
.map(DirEntry::into_path)
.collect();
// cargo version
println!(
"Using {}",
get_version_string("cargo", &["+nightly"]).await?
);
// rustfmt version
println!(
"Using {}",
get_version_string("cargo", &["+nightly", "fmt"]).await?
);
let reference_clang_format = format!(
"clang-format-{}",
std::env::var("MAIN_LLVM_VERSION")
.inspect(|e| {
println!(
"Overriding clang-format version from the default {REF_LLVM_VERSION} to {e} using env variable MAIN_LLVM_VERSION"
);
})
.unwrap_or(REF_LLVM_VERSION.to_string())
);
let unspecified_clang_format = "clang-format";
let (clang, version, warning) = if which(&reference_clang_format).is_ok() {
(
Some(reference_clang_format.as_str()),
Some(get_version_string(&reference_clang_format, &[]).await?),
None,
)
} else if which(unspecified_clang_format).is_ok() {
let version = get_version_string(unspecified_clang_format, &[]).await?;
(
Some(unspecified_clang_format),
Some(version.clone()),
Some(format!(
"using {version}, could provide a different result from {reference_clang_format}"
)),
)
} else {
(
None,
None,
Some("clang-format not found. Skipping C formatting...".to_string()),
)
};
if let Some(version) = &version {
println!("Using {version}");
}
let mut tokio_joinset = JoinSet::new();
for project in rust_projects_to_fmt {
tokio_joinset.spawn(run_cargo_fmt(project, cli.check, cli.verbose));
}
if let Some(clang) = clang {
let c_files_to_fmt: Vec<PathBuf> = WalkDir::new(&libafl_root_dir)
.into_iter()
.filter_map(Result::ok)
.filter(|e| !c_excluded_directories.is_match(e.path().as_os_str().to_str().unwrap()))
.filter(|e| e.file_type().is_file())
.filter(|e| c_file_to_format.is_match(e.file_name().to_str().unwrap()))
.map(DirEntry::into_path)
.collect();
for c_file in c_files_to_fmt {
tokio_joinset.spawn(run_clang_fmt(
c_file,
clang.to_string(),
cli.check,
cli.verbose,
));
}
}
while let Some(res) = tokio_joinset.join_next().await {
match res? {
Ok(()) => {}
Err(err) => {
println!("Error: {err}");
std::process::exit(exitcode::IOERR)
}
}
}
let _ = warning.map(print_warning);
if cli.check {
println!("[*] Check finished successfully.");
} else {
println!("[*] Formatting finished successfully.");
}
Ok(())
}
async fn get_version_string(path: &str, args: &[&str]) -> Result<String, io::Error> {
let version = Command::new(path)
.args(args)
.arg("--version")
.output()
.await?
.stdout;
Ok(from_utf8(&version).unwrap().replace('\n', ""))
}
#[allow(clippy::needless_pass_by_value)]
fn print_warning(warning: String) {
println!("\n{} {}\n", "Warning:".yellow().bold(), warning);
}