From 8a8fbb5cb4142f178375fc5712edb017748fcb62 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Thu, 21 Aug 2025 05:26:25 -0400 Subject: [PATCH 1/9] Add .rgignore --- .rgignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .rgignore diff --git a/.rgignore b/.rgignore new file mode 100644 index 000000000..3fd06b215 --- /dev/null +++ b/.rgignore @@ -0,0 +1 @@ +cargo-afl/AFLplusplus From f991c63531dbac3073a74816338a3dd1c0020d01 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sat, 16 Aug 2025 06:33:10 -0400 Subject: [PATCH 2/9] `plugins_available` -> `plugins_installed` --- cargo-afl/src/common.rs | 2 +- cargo-afl/src/config.rs | 2 +- cargo-afl/src/main.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cargo-afl/src/common.rs b/cargo-afl/src/common.rs index 0ea99ca32..a3d457025 100644 --- a/cargo-afl/src/common.rs +++ b/cargo-afl/src/common.rs @@ -66,7 +66,7 @@ pub fn archive_file_path() -> Result { afl_llvm_dir().map(|path| path.join("libafl-llvm-rt.a")) } -pub fn plugins_available() -> Result { +pub fn plugins_installed() -> Result { let afl_llvm_dir = afl_llvm_dir()?; for result in afl_llvm_dir .read_dir() diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs index 03e2f4dc0..151f25e13 100644 --- a/cargo-afl/src/config.rs +++ b/cargo-afl/src/config.rs @@ -38,7 +38,7 @@ pub struct Args { pub fn config(args: &Args) -> Result<()> { let archive_file_path = common::archive_file_path()?; - if !args.force && archive_file_path.exists() && args.plugins == common::plugins_available()? { + if !args.force && archive_file_path.exists() && args.plugins == common::plugins_installed()? { let version = common::afl_rustc_version()?; bail!( "AFL LLVM runtime was already built for Rust {version}; run `cargo afl config --build \ diff --git a/cargo-afl/src/main.rs b/cargo-afl/src/main.rs index ae37822de..63f238999 100644 --- a/cargo-afl/src/main.rs +++ b/cargo-afl/src/main.rs @@ -182,7 +182,7 @@ fn command_with_afl_version() -> clap::Command { (|| -> Option<()> { let afl_version = afl_version()?; - let with_plugins = common::plugins_available().ok()?; + let with_plugins = common::plugins_installed().ok()?; let subcmd = command.find_subcommand_mut("afl").unwrap(); let ver = format!( @@ -292,7 +292,7 @@ where environment_variables.insert("ASAN_OPTIONS", asan_options); environment_variables.insert("TSAN_OPTIONS", tsan_options); - let has_plugins = common::plugins_available().unwrap(); + let has_plugins = common::plugins_installed().unwrap(); if require_plugins || has_plugins { // Make sure we are on nightly for the -Z flags assert!( From 2aa211ddd860dc476d69cca334942bde6cecb46d Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Thu, 21 Aug 2025 05:33:01 -0400 Subject: [PATCH 3/9] Remove comment from `data_dir` --- cargo-afl/src/common.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cargo-afl/src/common.rs b/cargo-afl/src/common.rs index a3d457025..0a73e9e18 100644 --- a/cargo-afl/src/common.rs +++ b/cargo-afl/src/common.rs @@ -14,14 +14,6 @@ fn xdg_dir() -> Result { } fn data_dir(dir_name: &str) -> Result { - // For docs.rs builds, use OUT_DIR. - // For other cases, use a XDG data directory. - // It is necessary to use OUT_DIR for docs.rs builds, - // as that is the only place where we can write to. - // The Cargo documentation recommends that build scripts - // place their generated files at OUT_DIR too, but we - // don't change that for now for normal builds. - // smoelius: AFL++ is no longer built on docs.rs. let xdg_dir = xdg_dir()?; xdg_dir.create_data_directory(dir_name).map_err(Into::into) } From 725f486902ef4f5346c617a64308d2b43638e9e3 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Thu, 21 Aug 2025 05:35:37 -0400 Subject: [PATCH 4/9] Bring back `make clean` --- cargo-afl/src/config.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs index 151f25e13..3b34c778d 100644 --- a/cargo-afl/src/config.rs +++ b/cargo-afl/src/config.rs @@ -92,13 +92,11 @@ pub fn config(args: &Args) -> Result<()> { fn build_afl(args: &Args, work_dir: &Path) -> Result<()> { // if you had already installed cargo-afl previously you **must** clean AFL++ - // smoelius: AFL++ is now copied to a temporary directory before being built. So `make clean` - // is no longer necessary. let afl_dir = common::afl_dir()?; let mut command = Command::new("make"); command .current_dir(work_dir) - .arg("install") + .args(["clean", "install"]) // skip the checks for the legacy x86 afl-gcc compiler .env("AFL_NO_X86", "1") .env("DESTDIR", afl_dir) @@ -120,7 +118,11 @@ fn build_afl(args: &Args, work_dir: &Path) -> Result<()> { } let success = command.status().as_ref().is_ok_and(ExitStatus::success); - ensure!(success, "could not run 'make install'"); + ensure!( + success, + "could not run 'make clean install' in {}", + work_dir.display() + ); Ok(()) } From eb70a82980ec4f4f58720446ca91a45f5fa826ef Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Thu, 21 Aug 2025 05:51:46 -0400 Subject: [PATCH 5/9] Rework how directories are constructed in common.rs --- cargo-afl/src/common.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cargo-afl/src/common.rs b/cargo-afl/src/common.rs index 0a73e9e18..b0aa64d1d 100644 --- a/cargo-afl/src/common.rs +++ b/cargo-afl/src/common.rs @@ -5,17 +5,21 @@ use std::env; use std::ffi::OsStr; use std::path::{Path, PathBuf}; -fn xdg_dir() -> Result { - let afl_rustc_version = afl_rustc_version()?; - let prefix = Path::new("afl.rs") - .join(afl_rustc_version) - .join(pkg_version()); - Ok(xdg::BaseDirectories::with_prefix(prefix)) +/// Return the [`xdg::BaseDirectories`] used by afl.rs +/// +/// This function is public only for tests. Non-test code should use [`data_dir`], etc. +pub fn xdg_base_dir() -> xdg::BaseDirectories { + xdg::BaseDirectories::with_prefix("afl.rs") } fn data_dir(dir_name: &str) -> Result { - let xdg_dir = xdg_dir()?; - xdg_dir.create_data_directory(dir_name).map_err(Into::into) + let afl_rustc_version = afl_rustc_version()?; + let subdir = PathBuf::from(afl_rustc_version) + .join(pkg_version()) + .join(dir_name); + xdg_base_dir() + .create_data_directory(subdir) + .map_err(Into::into) } const SHORT_COMMIT_HASH_LEN: usize = 7; From bab64b4d176fbdcf95e194eebbde526f919fc4e3 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Thu, 21 Aug 2025 12:50:55 -0400 Subject: [PATCH 6/9] Add `copy_aflplusplus_submodule` --- cargo-afl/src/config.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs index 3b34c778d..1c71a5ee1 100644 --- a/cargo-afl/src/config.rs +++ b/cargo-afl/src/config.rs @@ -59,17 +59,7 @@ pub fn config(args: &Args) -> Result<()> { .is_ok_and(ExitStatus::success); ensure!(success, "could not run 'git'"); } else { - let success = Command::new("cp") - .args([ - "-P", // preserve symlinks - "-R", // copy directories recursively - afl_src_dir_str, - &*tempdir.path().to_string_lossy(), - ]) - .status() - .as_ref() - .is_ok_and(ExitStatus::success); - ensure!(success, "could not copy directory {afl_src_dir:?}"); + copy_aflplusplus_submodule(&tempdir.path().join(AFL_SRC_PATH))?; } let work_dir = tempdir.path().join(AFL_SRC_PATH); @@ -90,6 +80,30 @@ pub fn config(args: &Args) -> Result<()> { Ok(()) } +fn copy_aflplusplus_submodule(aflplusplus_dir: &Path) -> Result<()> { + let afl_src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join(AFL_SRC_PATH); + let afl_src_dir_str = &afl_src_dir.to_string_lossy(); + + let Some(aflplusplus_dir_parent) = aflplusplus_dir.parent() else { + bail!("could not get AFLplusplus dir parent"); + }; + debug_assert_eq!(aflplusplus_dir_parent.join(AFL_SRC_PATH), aflplusplus_dir); + + let success = Command::new("cp") + .args([ + "-P", // preserve symlinks + "-R", // copy directories recursively + afl_src_dir_str, + &*aflplusplus_dir_parent.to_string_lossy(), + ]) + .status() + .as_ref() + .is_ok_and(ExitStatus::success); + ensure!(success, "could not copy directory {afl_src_dir:?}"); + + Ok(()) +} + fn build_afl(args: &Args, work_dir: &Path) -> Result<()> { // if you had already installed cargo-afl previously you **must** clean AFL++ let afl_dir = common::afl_dir()?; From 7e839a9439db0e4b7c2c1e1282feee4e8a44a383 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Fri, 22 Aug 2025 13:57:23 -0400 Subject: [PATCH 7/9] Change how AFL++ source is managed --- cargo-afl/src/common.rs | 13 ++++ cargo-afl/src/config.rs | 162 ++++++++++++++++++++++++++++++++++------ cargo-afl/src/main.rs | 2 +- 3 files changed, 152 insertions(+), 25 deletions(-) diff --git a/cargo-afl/src/common.rs b/cargo-afl/src/common.rs index b0aa64d1d..323848ad2 100644 --- a/cargo-afl/src/common.rs +++ b/cargo-afl/src/common.rs @@ -62,6 +62,19 @@ pub fn archive_file_path() -> Result { afl_llvm_dir().map(|path| path.join("libafl-llvm-rt.a")) } +pub fn aflplusplus_dir() -> Result { + aflplusplus_dir_from_base_dir(&xdg_base_dir()) +} + +/// Construct the AFLplusplus directory from [`xdg::BaseDirectories`] +/// +/// This function exists only for tests. Non-test code should use [`aflplusplus_dir`]. +pub fn aflplusplus_dir_from_base_dir(base_dir: &xdg::BaseDirectories) -> Result { + base_dir + .create_data_directory("AFLplusplus") + .map_err(Into::into) +} + pub fn plugins_installed() -> Result { let afl_llvm_dir = afl_llvm_dir()?; for result in afl_llvm_dir diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs index 1c71a5ee1..14e8455f6 100644 --- a/cargo-afl/src/config.rs +++ b/cargo-afl/src/config.rs @@ -1,5 +1,3 @@ -#![deny(clippy::disallowed_macros, clippy::expect_used, clippy::unwrap_used)] - use anyhow::{Context, Result, bail, ensure}; use clap::Parser; use std::ffi::OsStr; @@ -9,6 +7,7 @@ use std::process::{Command, ExitStatus, Stdio}; use super::common; const AFL_SRC_PATH: &str = "AFLplusplus"; +const AFLPLUSPLUS_URL: &str = "https://github.com/AFLplusplus/AFLplusplus"; // https://github.com/rust-fuzz/afl.rs/issues/148 #[cfg(target_os = "macos")] @@ -26,19 +25,41 @@ pub struct Args { #[clap(long, help = "Build AFL++ for the default toolchain")] pub build: bool, - #[clap(long, help = "Rebuild AFL++ if it was already built")] + #[clap( + long, + help = "Rebuild AFL++ if it was already built. Note: AFL++ will be built without plugins \ + if `--plugins` is not passed." + )] pub force: bool, #[clap(long, help = "Enable building of LLVM plugins")] pub plugins: bool, + #[clap( + long, + help = "Update to instead of the latest stable version", + requires = "update" + )] + pub tag: Option, + + #[clap( + long, + help = "Update AFL++ to the latest stable version (preserving plugins, if applicable)" + )] + pub update: bool, + #[clap(long, help = "Show build output")] pub verbose: bool, } pub fn config(args: &Args) -> Result<()> { let archive_file_path = common::archive_file_path()?; - if !args.force && archive_file_path.exists() && args.plugins == common::plugins_installed()? { + + if !args.force + && !args.update + && archive_file_path.exists() + && args.plugins == common::plugins_installed()? + { let version = common::afl_rustc_version()?; bail!( "AFL LLVM runtime was already built for Rust {version}; run `cargo afl config --build \ @@ -46,40 +67,111 @@ pub fn config(args: &Args) -> Result<()> { ); } - let afl_src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join(AFL_SRC_PATH); - let afl_src_dir_str = &afl_src_dir.to_string_lossy(); - - let tempdir = tempfile::tempdir().with_context(|| "could not create temporary directory")?; + // smoelius: If updating and AFL++ was built with plugins before, build with plugins again. + let args = Args { + plugins: if args.update { + common::plugins_installed().is_ok_and(|is_true| is_true) + } else { + args.plugins + }, + tag: args.tag.clone(), + ..*args + }; - if afl_src_dir.join(".git").is_dir() { - let success = Command::new("git") - .args(["clone", afl_src_dir_str, &*tempdir.path().to_string_lossy()]) - .status() - .as_ref() - .is_ok_and(ExitStatus::success); - ensure!(success, "could not run 'git'"); - } else { - copy_aflplusplus_submodule(&tempdir.path().join(AFL_SRC_PATH))?; + let aflplusplus_dir = + common::aflplusplus_dir().with_context(|| "could not determine AFLplusplus directory")?; + + // smoelius: The AFLplusplus directory could be in one of three possible states: + // + // 1. Nonexistent + // 2. Initialized with a copy of the AFLplusplus submodule from afl.rs's source tree + // 3. Cloned from `AFLPLUSPLUS_URL` + // + // If we are not updating and the AFLplusplus directory is nonexistent: initialize the directory + // with a copy of the AFLplusplus submodule from afl.rs's source tree (the `else` case in the + // next `if` statement). + // + // If we are updating and the AFLplusplus directory is a copy of the AFLplusplus submodule from + // afl.rs's source tree: remove it and create a new directory by cloning AFL++ (the `else` case + // in `update_to_stable_or_tag`). + // + // Finally, if we are updating: check out either `origin/stable` or the tag that was passed. + if args.update { + let rev_prev = if is_repo(&aflplusplus_dir)? { + rev(&aflplusplus_dir).map(Some)? + } else { + None + }; + + update_to_stable_or_tag(&aflplusplus_dir, args.tag.as_deref())?; + + let rev_curr = rev(&aflplusplus_dir)?; + + if rev_prev == Some(rev_curr) && !args.force { + eprintln!("Nothing to do. Pass `--force` to force rebuilding."); + return Ok(()); + } + } else if !aflplusplus_dir.join(".git").try_exists()? { + copy_aflplusplus_submodule(&aflplusplus_dir)?; } - let work_dir = tempdir.path().join(AFL_SRC_PATH); - - build_afl(args, &work_dir)?; - build_afl_llvm_runtime(args, &work_dir)?; + build_afl(&args, &aflplusplus_dir)?; + build_afl_llvm_runtime(&args, &aflplusplus_dir)?; if args.plugins { - copy_afl_llvm_plugins(args, &work_dir)?; + copy_afl_llvm_plugins(&args, &aflplusplus_dir)?; } let afl_dir = common::afl_dir()?; - let Some(dir) = afl_dir.parent().map(Path::to_path_buf) else { + let Some(afl_dir_parent) = afl_dir.parent() else { bail!("could not get afl dir parent"); }; - eprintln!("Artifacts written to {}", dir.display()); + eprintln!("Artifacts written to {}", afl_dir_parent.display()); Ok(()) } +fn update_to_stable_or_tag(aflplusplus_dir: &Path, tag: Option<&str>) -> Result<()> { + if is_repo(aflplusplus_dir)? { + let success = Command::new("git") + .arg("fetch") + .current_dir(aflplusplus_dir) + .status() + .as_ref() + .is_ok_and(ExitStatus::success); + ensure!(success, "could not run 'git fetch'"); + } else { + remove_aflplusplus_dir(aflplusplus_dir).unwrap_or_default(); + let success = Command::new("git") + .args([ + "clone", + AFLPLUSPLUS_URL, + &*aflplusplus_dir.to_string_lossy(), + ]) + .status() + .as_ref() + .is_ok_and(ExitStatus::success); + ensure!(success, "could not run 'git clone'"); + } + + let mut command = Command::new("git"); + command.arg("checkout"); + if let Some(tag) = tag { + command.arg(tag); + } else { + command.arg("origin/stable"); + } + command.current_dir(aflplusplus_dir); + let success = command.status().as_ref().is_ok_and(ExitStatus::success); + ensure!(success, "could not run 'git checkout'"); + + Ok(()) +} + +fn remove_aflplusplus_dir(aflplusplus_dir: &Path) -> Result<()> { + std::fs::remove_dir_all(aflplusplus_dir).map_err(Into::into) +} + fn copy_aflplusplus_submodule(aflplusplus_dir: &Path) -> Result<()> { let afl_src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join(AFL_SRC_PATH); let afl_src_dir_str = &afl_src_dir.to_string_lossy(); @@ -104,6 +196,28 @@ fn copy_aflplusplus_submodule(aflplusplus_dir: &Path) -> Result<()> { Ok(()) } +// smoelius: `dot_git` will refer to an ASCII text file if it was copied from the AFLplusplus +// submodule from afl.rs's source tree. +fn is_repo(aflplusplus_dir: &Path) -> Result { + let dot_git = aflplusplus_dir.join(".git"); + if dot_git.try_exists()? { + Ok(dot_git.is_dir()) + } else { + Ok(false) + } +} + +fn rev(dir: &Path) -> Result { + let mut command = Command::new("git"); + command.args(["rev-parse", "HEAD"]); + command.current_dir(dir); + let output = command + .output() + .with_context(|| "could not run `git rev-parse`")?; + ensure!(output.status.success(), "`git rev-parse` failed"); + String::from_utf8(output.stdout).map_err(Into::into) +} + fn build_afl(args: &Args, work_dir: &Path) -> Result<()> { // if you had already installed cargo-afl previously you **must** clean AFL++ let afl_dir = common::afl_dir()?; diff --git a/cargo-afl/src/main.rs b/cargo-afl/src/main.rs index 63f238999..3309700b0 100644 --- a/cargo-afl/src/main.rs +++ b/cargo-afl/src/main.rs @@ -104,7 +104,7 @@ declare_afl_subcommand_enum! { Addseeds("Invoke afl-addseeds"), Analyze("Invoke afl-analyze"), Cmin("Invoke afl-cmin"), - Config("Build or rebuild AFL++", config::Args), + Config("Build, rebuild, or update AFL++", config::Args), Fuzz("Invoke afl-fuzz"), Gotcpu("Invoke afl-gotcpu"), Plot("Invoke afl-plot"), From 2b65a6b2de1dccdccbcc8d535b746dd0c84e522a Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Fri, 22 Aug 2025 13:58:22 -0400 Subject: [PATCH 8/9] Add tests --- cargo-afl/src/config.rs | 138 ++++++++++++++++++++++++++++++++++++++++ cargo-afl/src/main.rs | 10 +++ 2 files changed, 148 insertions(+) diff --git a/cargo-afl/src/config.rs b/cargo-afl/src/config.rs index 14e8455f6..c8dbd9b32 100644 --- a/cargo-afl/src/config.rs +++ b/cargo-afl/src/config.rs @@ -347,3 +347,141 @@ fn check_llvm_and_get_config() -> Result { Ok(llvm_config) } + +#[cfg(test)] +mod tests { + use super::{copy_aflplusplus_submodule, remove_aflplusplus_dir, update_to_stable_or_tag}; + use crate::{common, config::is_repo}; + use anyhow::Result; + use assert_cmd::cargo::CommandCargoExt; + use std::{path::Path, process::Command}; + use tempfile::tempdir; + + #[derive(Clone, Copy, Debug)] + enum State { + Nonexistent, + Submodule, + Tag(&'static str), + Stable, + } + + const TESTCASES: &[(State, State, &[&str])] = &[ + // smoelius: There is currently no way to update to the submodule. + // (State::Nonexistent, State::Submodule, &[]), + ( + State::Nonexistent, + State::Tag("v4.33c"), + &[ + #[cfg(not(target_os = "macos"))] + "Note: switching to 'v4.33c'.", + "HEAD is now at", + ], + ), + ( + State::Nonexistent, + State::Stable, + &[ + #[cfg(not(target_os = "macos"))] + "Note: switching to 'origin/stable'.", + "HEAD is now at", + ], + ), + ( + State::Submodule, + State::Tag("v4.33c"), + &[ + #[cfg(not(target_os = "macos"))] + "Note: switching to 'v4.33c'.", + "HEAD is now at", + ], + ), + ( + State::Submodule, + State::Stable, + &[ + #[cfg(not(target_os = "macos"))] + "Note: switching to 'origin/stable'.", + "HEAD is now at", + ], + ), + // smoelius: It should be possible to go from a tag to the stable version. + ( + State::Tag("v4.33c"), + State::Stable, + &["Previous HEAD position was", "HEAD is now at"], + ), + // smoelius: It should be possible to go from the stable version to a tag. + ( + State::Stable, + State::Tag("v4.33c"), + &["Previous HEAD position was", "HEAD is now at"], + ), + ]; + + #[test] + fn update() { + let mut base_dir = common::xdg_base_dir(); + + for &(before, after, line_prefixes) in TESTCASES { + eprintln!("{before:?} -> {after:?}"); + + let tempdir = tempdir().unwrap(); + + // smoelius: Based on https://github.com/whitequark/rust-xdg/issues/44, the recommended + // way of testing with a fake value of `XDG_DATA_HOME` seems to be manually overwriting + // the `data_home` field in `xdg::BaseDirectories`. + base_dir.data_home = Some(tempdir.path().to_path_buf()); + + let aflplusplus_dir = common::aflplusplus_dir_from_base_dir(&base_dir).unwrap(); + + assert!(aflplusplus_dir.starts_with(tempdir.path())); + + set_aflplusplus_dir_contents(before, &aflplusplus_dir).unwrap(); + + let mut command = Command::cargo_bin("cargo-afl").unwrap(); + command.args(["afl", "config", "--update"]); + command.env("XDG_DATA_HOME", tempdir.path()); + match after { + State::Nonexistent | State::Submodule => unreachable!(), + State::Tag(tag) => { + command.args(["--tag", tag]); + } + State::Stable => {} + } + let output = command.output().unwrap(); + assert!(output.status.success()); + let stderr = String::from_utf8(output.stderr).unwrap(); + contains_expected_line_prefixes(&stderr, line_prefixes); + } + } + + fn set_aflplusplus_dir_contents(state: State, aflplusplus_dir: &Path) -> Result<()> { + let result = match state { + State::Nonexistent => remove_aflplusplus_dir(aflplusplus_dir), + State::Submodule => copy_aflplusplus_submodule(aflplusplus_dir), + State::Tag(tag) => update_to_stable_or_tag(aflplusplus_dir, Some(tag)), + State::Stable => update_to_stable_or_tag(aflplusplus_dir, None), + }; + // smoelius: Sanity. + assert!( + is_repo(aflplusplus_dir) + .is_ok_and(|value| value == matches!(state, State::Tag(_) | State::Stable)) + ); + result + } + + fn contains_expected_line_prefixes(stderr: &str, mut line_prefixes: &[&str]) { + for line in stderr.lines() { + if line_prefixes + .first() + .is_some_and(|prefix| line.starts_with(prefix)) + { + line_prefixes = &line_prefixes[1..]; + } + } + assert!( + line_prefixes.is_empty(), + "Could not find line prefix {line_prefixes:?}:\n```\n{stderr}```" + ); + } +} diff --git a/cargo-afl/src/main.rs b/cargo-afl/src/main.rs index 3309700b0..f12a58b19 100644 --- a/cargo-afl/src/main.rs +++ b/cargo-afl/src/main.rs @@ -535,6 +535,16 @@ mod tests { } } + #[test] + fn tag_requires_update() { + let output = cargo_afl(&["config", "--tag", "v4.33c"]).output().unwrap(); + assert_failure(&output, None); + assert!(String::from_utf8(output.stderr).unwrap().contains( + "error: the following required arguments were not provided: + --update" + )); + } + fn cargo_afl>(args: &[T]) -> Command { let mut command = command(); command.arg("afl").args(args).env("NO_SUDO", "1"); From 4041838be89b4a2a586c2d60d241733876e986b0 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Fri, 22 Aug 2025 13:58:43 -0400 Subject: [PATCH 9/9] Make Clippy happy --- clippy.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy.toml b/clippy.toml index e2f44cbe0..57944a8de 100644 --- a/clippy.toml +++ b/clippy.toml @@ -4,3 +4,4 @@ disallowed-macros = [ "std::assert_ne", "std::panic", ] +doc-valid-idents = ["AFLplusplus"]