xtask\tasks\fuzz/
cargo_fuzz.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Glue to invoke external `cargo-fuzz` commands
5
6use anyhow::Context;
7use std::path::Path;
8use std::path::PathBuf;
9
10pub(super) enum CargoFuzzCommand {
11    Build,
12    Run { artifact: Option<PathBuf> },
13    Fmt { input: PathBuf },
14    Cmin,
15    Tmin { test_case: PathBuf },
16    Coverage,
17}
18
19impl CargoFuzzCommand {
20    fn to_args<'a, 'b: 'a>(&'b self, target: &'a str) -> Vec<&'a str> {
21        match self {
22            CargoFuzzCommand::Build => {
23                vec!["build", target]
24            }
25            CargoFuzzCommand::Run { artifact } => {
26                let mut args = vec!["run", target];
27                if let Some(artifact) = artifact {
28                    args.push(artifact.to_str().unwrap())
29                }
30                args
31            }
32            CargoFuzzCommand::Fmt { input } => {
33                vec!["fmt", target, input.to_str().unwrap()]
34            }
35            CargoFuzzCommand::Cmin => {
36                vec!["cmin", target]
37            }
38            CargoFuzzCommand::Tmin { test_case } => {
39                vec!["tmin", target, test_case.to_str().unwrap()]
40            }
41            CargoFuzzCommand::Coverage => {
42                vec!["coverage", target]
43            }
44        }
45    }
46
47    pub(super) fn invoke(
48        self,
49        target_name: &str,
50        fuzz_dir: &Path,
51        target_options: &[String],
52        toolchain: Option<&str>,
53        extra: &[String],
54    ) -> anyhow::Result<()> {
55        if which::which("cargo-fuzz").is_err() {
56            anyhow::bail!("could not find cargo-fuzz! did you run `cargo install cargo-fuzz`?");
57        }
58
59        let sh = xshell::Shell::new()?;
60        if matches!(&self, CargoFuzzCommand::Run { artifact: Some(_) }) {
61            sh.set_var("XTASK_FUZZ_REPRO", "1");
62        }
63
64        let mut toolchain_check_cmd = xshell::cmd!(sh, "rustc");
65        if let Some(toolchain_override) = toolchain {
66            toolchain_check_cmd = toolchain_check_cmd.arg(format!("+{}", toolchain_override));
67        }
68        let result = toolchain_check_cmd
69            .arg("-V")
70            .output()
71            .context("could not detect toolchain! did you run `rustup toolchain install`?")?;
72        let output = std::str::from_utf8(&result.stdout)?.to_ascii_lowercase();
73        let is_nightly = output.contains("-nightly") || output.contains("-dev");
74
75        let mut cmd = xshell::cmd!(sh, "cargo");
76        if let Some(toolchain_override) = toolchain {
77            cmd = cmd.arg(format!("+{}", toolchain_override));
78        }
79        cmd = cmd.arg("fuzz");
80        cmd = cmd.args(self.to_args(target_name));
81        cmd = cmd.arg("--fuzz-dir").arg(fuzz_dir);
82
83        if is_nightly {
84            // Sanitizers can be enabled, leave defaults alone
85        } else if std::env::var_os("CARGO").is_some() {
86            // We are running in a stable toolchain `cargo xtask` invocation.
87            // Cargo prevents us from setting RUSTC_BOOTSTRAP for a nested
88            // invocation, so we can't enable sanitizers.
89            log::warn!(
90                "Running on a stable toolchain in a `cargo xtask` invocation, disabling sanitizers"
91            );
92            log::warn!(
93                "To enable sanitizers, run {} directly, or switch to a nightly toolchain",
94                std::env::current_exe()?.display()
95            );
96            cmd = cmd.args(["-s", "none"]);
97        } else {
98            // Non-cargo invocation, sanitizers can be enabled via RUSTC_BOOTSTRAP
99            log::warn!("Running on a stable toolchain, enabling sanitizers via RUSTC_BOOTSTRAP");
100            cmd = cmd.env("RUSTC_BOOTSTRAP", "1");
101        }
102
103        cmd = cmd.args(extra);
104        if self.supports_target_options() && !target_options.is_empty() {
105            if !extra.iter().any(|x| x == "--") {
106                cmd = cmd.arg("--");
107            }
108            cmd = cmd.args(target_options);
109        }
110
111        cmd.run()?;
112
113        Ok(())
114    }
115
116    fn supports_target_options(&self) -> bool {
117        matches!(self, CargoFuzzCommand::Run { .. })
118    }
119}