blob: 2d0ca3b932a48c830c5be6a11853cabe5e3760fa [file] [log] [blame] [edit]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{
collections::HashMap,
path::{self, PathBuf},
};
use serde::Deserialize;
use xshell::{cmd, Shell};
/// Partial structure for parsing the output of `cargo metadata --format-version=1`.
///
/// This doesn't contain all the fields in `cargo metadata`, just the ones we need.
#[derive(Deserialize)]
struct CargoMetadata {
packages: Vec<CargoMetadataPackage>,
}
#[derive(Deserialize)]
struct CargoMetadataPackage {
manifest_path: String,
metadata: Option<HashMap<String, serde_json::Value>>,
targets: Vec<CargoMetadataPackageTarget>,
}
#[derive(Deserialize)]
struct CargoMetadataPackageTarget {
name: String,
kind: Vec<String>,
}
/// A `bin` target defined for fuzzing. See [`iter_fuzz_binaries`].
#[derive(Debug)]
pub struct FuzzTarget {
pub fuzz_dir: PathBuf,
pub target_name: String,
}
impl FuzzTarget {
/// Run the fuzz target using `cargo fuzz run`.
pub fn run(&self, sh: &xshell::Shell) -> xshell::Result<()> {
let FuzzTarget {
fuzz_dir,
target_name,
} = self;
cmd!(
sh,
"cargo +nightly fuzz run --fuzz-dir {fuzz_dir} {target_name} -- -runs=1000000 -max_total_time=30"
)
.run()
}
}
/// Create an iterator over all of the fuzz targets defined in the workspace at `root`.
///
/// This iterator discovers targets using `cargo metadata`. It looks for crates with the metadata
/// `cargo-fuzz = true` in its Cargo.toml, and looks for `[[bin]]` targets in all of those crates.
pub fn iter_fuzz_binaries(root: &path::Path) -> anyhow::Result<impl Iterator<Item = FuzzTarget>> {
let sh = Shell::new()?;
sh.change_dir(root);
let cargo_metadata = cmd!(sh, "cargo metadata --no-deps --format-version=1").read()?;
let metadata_json: CargoMetadata = serde_json::from_str(&cargo_metadata)?;
let packages = metadata_json.packages.into_iter().filter_map(|package| {
let metadata_map = package.metadata.as_ref()?;
metadata_map
.get("cargo-fuzz")?
.as_bool()?
.then_some(package)
});
Ok(packages.flat_map(|package| {
let fuzz_manifest = PathBuf::from(&package.manifest_path);
let fuzz_dir = fuzz_manifest
.parent()
.expect("Fuzz manifest should have a parent directory")
.to_owned();
package
.targets
.into_iter()
.filter(|t| t.kind.contains(&String::from("bin")))
.map(move |target| FuzzTarget {
fuzz_dir: fuzz_dir.clone(),
target_name: target.name.to_string(),
})
}))
}
/// Runs all of the fuzz targets defined within the workspace `root`.
pub fn run_workspace_fuzz_targets(root: &path::Path) -> anyhow::Result<()> {
log::info!("Running rust fuzzers");
let sh = Shell::new()?;
sh.change_dir(root);
let fuzz_targets: Vec<_> = iter_fuzz_binaries(root)?.collect();
log::info!(
"Fuzzing on {} targets. This may take up to {} seconds.",
fuzz_targets.len(),
30 * fuzz_targets.len()
);
for fuzz_target in fuzz_targets {
fuzz_target.run(&sh)?;
}
Ok(())
}