flux_config/
flags.rs

1use std::{env, path::PathBuf, process, sync::LazyLock};
2
3use serde::Deserialize;
4pub use toml::Value;
5
6use crate::{PointerWidth, SmtSolver};
7
8const FLUX_FLAG_PREFIX: &str = "-F";
9
10/// Exit status code used for invalid flags.
11pub const EXIT_FAILURE: i32 = 2;
12
13pub struct Flags {
14    /// Sets the directory to dump data. Defaults to `./log/`.
15    pub log_dir: PathBuf,
16    /// Only checks definitions containing `name` as a substring
17    pub check_def: String,
18    /// Only checks the specified files. Takes a list of comma separated paths
19    pub check_files: Paths,
20    /// Set the pointer size (either `32` or `64`), used to determine if an integer cast is lossy
21    /// (default `64`).
22    pub pointer_width: PointerWidth,
23    /// If present switches on query caching and saves the cache in the provided path
24    pub cache: Option<PathBuf>,
25    pub verbose: bool,
26    /// Compute statistics about number and size of annotations. Dumps file to [`Self.log_dir`]
27    pub annots: bool,
28    /// Print statistics about time taked to analyze each fuction. Also dumps a file with the raw
29    /// times for each function.
30    pub timings: bool,
31    /// Default solver. Either `z3` or `cvc5`.
32    pub solver: SmtSolver,
33    /// Enables qualifier scrapping in fixpoint
34    pub scrape_quals: bool,
35    /// Translates _monomorphic_ `defs` functions into SMT `define-fun` instead of inlining them
36    /// away inside `flux`.
37    pub smt_define_fun: bool,
38    /// If true checks for over and underflow on arithmetic integer operations, default `false`. When
39    /// set to `false`, it still checks for underflow on unsigned integer subtraction.
40    pub check_overflow: bool,
41    /// Dump constraints generated for each function (debugging)
42    pub dump_constraint: bool,
43    /// Saves the checker's trace (debugging)
44    pub dump_checker_trace: bool,
45    /// Saves the `fhir` for each item (debugging)
46    pub dump_fhir: bool,
47    /// Saves the the `fhir` (debugging)
48    pub dump_rty: bool,
49    /// Saves the low-level MIR for each analyzed function (debugging)
50    pub dump_mir: bool,
51    /// Saves the low-level MIR for each analyzed function (debugging)
52    pub catch_bugs: bool,
53    /// Whether verification for the current crate is enabled. If false (the default), `flux-driver`
54    /// will behave exactly like `rustc`. This flag is managed by the `cargo flux` and `flux` binaries,
55    /// so you don't need to mess with it.
56    pub verify: bool,
57    /// If `true`, produce artifacts after analysis. This flag is managed by `cargo flux`, so you
58    /// don't typically have to set it manually.
59    pub full_compilation: bool,
60    /// If `true`, all code is trusted by default. You can selectively untrust items by marking them with `#[trusted(no)]`. The default value of this flag is `false`, i.e., all code is untrusted by default.
61    pub trusted_default: bool,
62    /// If `true`, all code will be ignored by default. You can selectively unignore items by marking them with `#[ignore(no)]`. The default value of this flag is `false`, i.e., all code is unignored by default.
63    pub ignore_default: bool,
64}
65
66impl Default for Flags {
67    fn default() -> Self {
68        Self {
69            log_dir: PathBuf::from("./log/"),
70            dump_constraint: false,
71            dump_checker_trace: false,
72            dump_fhir: false,
73            dump_rty: false,
74            dump_mir: false,
75            catch_bugs: false,
76            pointer_width: PointerWidth::default(),
77            check_def: String::new(),
78            check_files: Paths::default(),
79            cache: None,
80            check_overflow: false,
81            scrape_quals: false,
82            solver: SmtSolver::default(),
83            smt_define_fun: false,
84            verbose: false,
85            annots: false,
86            timings: false,
87            verify: false,
88            full_compilation: false,
89            trusted_default: false,
90            ignore_default: false,
91        }
92    }
93}
94
95pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
96    let mut flags = Flags::default();
97    for arg in env::args() {
98        let Some((key, value)) = parse_flux_arg(&arg) else { continue };
99
100        let result = match key {
101            "log-dir" => parse_path_buf(&mut flags.log_dir, value),
102            "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
103            "dump-checker-trace" => parse_bool(&mut flags.dump_checker_trace, value),
104            "dump-mir" => parse_bool(&mut flags.dump_mir, value),
105            "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
106            "dump-rty" => parse_bool(&mut flags.dump_rty, value),
107            "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
108            "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
109            "check-overflow" => parse_bool(&mut flags.check_overflow, value),
110            "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
111            "solver" => parse_solver(&mut flags.solver, value),
112            "verbose" => parse_bool(&mut flags.verbose, value),
113            "smt-define-fun" => parse_bool(&mut flags.smt_define_fun, value),
114            "annots" => parse_bool(&mut flags.annots, value),
115            "timings" => parse_bool(&mut flags.timings, value),
116            "cache" => parse_opt_path_buf(&mut flags.cache, value),
117            "check-def" => parse_string(&mut flags.check_def, value),
118            "check-files" => parse_check_files(&mut flags.check_files, value),
119            "verify" => parse_bool(&mut flags.verify, value),
120            "full-compilation" => parse_bool(&mut flags.full_compilation, value),
121            "trusted" => parse_bool(&mut flags.trusted_default, value),
122            "ignore" => parse_bool(&mut flags.ignore_default, value),
123            _ => {
124                eprintln!("error: unknown flux option: `{key}`");
125                process::exit(EXIT_FAILURE);
126            }
127        };
128        if let Err(reason) = result {
129            eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
130            process::exit(1);
131        }
132    }
133    flags
134});
135
136#[derive(Default)]
137pub struct Paths {
138    paths: Option<Vec<PathBuf>>,
139}
140
141impl Paths {
142    pub fn is_checked_file(&self, file: &str) -> bool {
143        self.paths
144            .as_ref()
145            .is_none_or(|p| p.iter().any(|p| p.to_str().unwrap() == file))
146    }
147}
148
149impl<'de> Deserialize<'de> for Paths {
150    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
151    where
152        D: serde::Deserializer<'de>,
153    {
154        let paths: Vec<PathBuf> = String::deserialize(deserializer)?
155            .split(",")
156            .map(str::trim)
157            .filter(|s| !s.is_empty())
158            .map(PathBuf::from)
159            .collect();
160
161        let paths = if paths.is_empty() { None } else { Some(paths) };
162
163        Ok(Paths { paths })
164    }
165}
166
167pub fn is_flux_arg(arg: &str) -> bool {
168    parse_flux_arg(arg).is_some()
169}
170
171fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
172    let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
173    if arg.is_empty() {
174        return None;
175    }
176    if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
177}
178
179fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
180    match v {
181        Some("y") | Some("yes") | Some("on") | Some("true") | None => {
182            *slot = true;
183            Ok(())
184        }
185        Some("n") | Some("no") | Some("off") | Some("false") => {
186            *slot = false;
187            Ok(())
188        }
189        _ => {
190            Err(
191                "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
192            )
193        }
194    }
195}
196
197fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
198    match v {
199        Some(s) => {
200            *slot = PathBuf::from(s);
201            Ok(())
202        }
203        None => Err("a path"),
204    }
205}
206
207fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
208    match v {
209        Some(s) => {
210            *slot = s.parse()?;
211            Ok(())
212        }
213        _ => Err(PointerWidth::ERROR),
214    }
215}
216
217fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
218    match v {
219        Some(s) => {
220            *slot = s.parse()?;
221            Ok(())
222        }
223        _ => Err(SmtSolver::ERROR),
224    }
225}
226
227fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
228    match v {
229        Some(s) => {
230            *slot = Some(PathBuf::from(s));
231            Ok(())
232        }
233        None => Err("a path"),
234    }
235}
236
237fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
238    match v {
239        Some(s) => {
240            *slot = s.to_string();
241            Ok(())
242        }
243        None => Err("a string"),
244    }
245}
246
247fn parse_check_files(slot: &mut Paths, v: Option<&str>) -> Result<(), &'static str> {
248    match v {
249        Some(s) => {
250            let paths: Vec<PathBuf> = s
251                .split(",")
252                .map(str::trim)
253                .filter(|s| !s.is_empty())
254                .map(PathBuf::from)
255                .collect();
256            *slot = Paths { paths: if paths.is_empty() { None } else { Some(paths) } };
257            Ok(())
258        }
259        None => Err("a comma separated list of paths"),
260    }
261}