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