flux_config/
flags.rs

1use std::{env, path::PathBuf, process, str::FromStr, sync::LazyLock};
2
3pub use toml::Value;
4use tracing::Level;
5
6use crate::{IncludePattern, OverflowMode, 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    /// If present, only check files matching the [`IncludePattern`] a glob pattern.
17    pub include: Option<IncludePattern>,
18    /// Set the pointer size (either `32` or `64`), used to determine if an integer cast is lossy
19    /// (default `64`).
20    pub pointer_width: PointerWidth,
21    /// If present switches on query caching and saves the cache in the provided path
22    pub cache: Option<PathBuf>,
23    /// Compute statistics about number and size of annotations. Dumps file to [`Self::log_dir`]
24    pub annots: bool,
25    /// Print statistics about time taken to analyze each fuction. Also dumps a file with the raw
26    /// times for each function.
27    pub timings: bool,
28    /// Print statistics about number of functions checked, trusted, etc.
29    pub summary: bool,
30    /// Default solver. Either `z3` or `cvc5`.
31    pub solver: SmtSolver,
32    /// Enables qualifier scrapping in fixpoint
33    pub scrape_quals: bool,
34    /// Enables uninterpreted casts
35    pub allow_uninterpreted_cast: bool,
36    /// Translates _monomorphic_ `defs` functions into SMT `define-fun` instead of inlining them
37    /// away inside `flux`.
38    pub smt_define_fun: bool,
39    /// If `strict` checks for over and underflow on arithmetic integer operations,
40    /// If `lazy` checks for underflow and loses information if possible overflow,
41    /// If `none` (default), it still checks for underflow on unsigned integer subtraction.
42    pub check_overflow: OverflowMode,
43    /// Dump constraints generated for each function (debugging)
44    pub dump_constraint: bool,
45    /// Saves the checker's trace (debugging)
46    pub dump_checker_trace: Option<tracing::Level>,
47    /// Saves the `fhir` for each item (debugging)
48    pub dump_fhir: bool,
49    /// Saves the the `fhir` (debugging)
50    pub dump_rty: bool,
51    /// Optimistically keeps running flux even after errors are found to get as many errors as possible
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    pub emit_lean_defs: bool,
65    /// If `true`, every function is implicitly labeled with a `no_panic` by default.
66    pub no_panic: bool,
67}
68
69impl Default for Flags {
70    fn default() -> Self {
71        Self {
72            log_dir: PathBuf::from("./log/"),
73            dump_constraint: false,
74            dump_checker_trace: None,
75            dump_fhir: false,
76            dump_rty: false,
77            catch_bugs: false,
78            pointer_width: PointerWidth::default(),
79            include: None,
80            cache: None,
81            check_overflow: OverflowMode::default(),
82            scrape_quals: false,
83            allow_uninterpreted_cast: false,
84            solver: SmtSolver::default(),
85            smt_define_fun: false,
86            annots: false,
87            timings: false,
88            summary: true,
89            verify: false,
90            full_compilation: false,
91            trusted_default: false,
92            ignore_default: false,
93            emit_lean_defs: false,
94            no_panic: false,
95        }
96    }
97}
98
99pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
100    let mut flags = Flags::default();
101    let mut includes: Vec<String> = Vec::new();
102    for arg in env::args() {
103        let Some((key, value)) = parse_flux_arg(&arg) else { continue };
104
105        let result = match key {
106            "log-dir" => parse_path_buf(&mut flags.log_dir, value),
107            "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
108            "dump-checker-trace" => parse_opt_level(&mut flags.dump_checker_trace, value),
109            "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
110            "dump-rty" => parse_bool(&mut flags.dump_rty, value),
111            "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
112            "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
113            "check-overflow" => parse_overflow(&mut flags.check_overflow, value),
114            "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
115            "allow-uninterpreted-cast" => parse_bool(&mut flags.allow_uninterpreted_cast, value),
116            "solver" => parse_solver(&mut flags.solver, value),
117            "smt-define-fun" => parse_bool(&mut flags.smt_define_fun, value),
118            "annots" => parse_bool(&mut flags.annots, value),
119            "timings" => parse_bool(&mut flags.timings, value),
120            "summary" => parse_bool(&mut flags.summary, value),
121            "cache" => parse_opt_path_buf(&mut flags.cache, value),
122            "include" => parse_opt_include(&mut includes, value),
123            "verify" => parse_bool(&mut flags.verify, value),
124            "full-compilation" => parse_bool(&mut flags.full_compilation, value),
125            "trusted" => parse_bool(&mut flags.trusted_default, value),
126            "ignore" => parse_bool(&mut flags.ignore_default, value),
127            "emit_lean_defs" => parse_bool(&mut flags.emit_lean_defs, value),
128            "no-panic" => parse_bool(&mut flags.no_panic, value),
129            _ => {
130                eprintln!("error: unknown flux option: `{key}`");
131                process::exit(EXIT_FAILURE);
132            }
133        };
134        if let Err(reason) = result {
135            eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
136            process::exit(1);
137        }
138    }
139    if !includes.is_empty() {
140        let include = IncludePattern::new(includes).unwrap_or_else(|err| {
141            eprintln!("error: invalid include pattern: {err}");
142            process::exit(1);
143        });
144        flags.include = Some(include);
145    }
146    flags
147});
148
149pub fn is_flux_arg(arg: &str) -> bool {
150    parse_flux_arg(arg).is_some()
151}
152
153fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
154    let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
155    if arg.is_empty() {
156        return None;
157    }
158    if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
159}
160
161fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
162    match v {
163        Some("y") | Some("yes") | Some("on") | Some("true") | None => {
164            *slot = true;
165            Ok(())
166        }
167        Some("n") | Some("no") | Some("off") | Some("false") => {
168            *slot = false;
169            Ok(())
170        }
171        _ => {
172            Err(
173                "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
174            )
175        }
176    }
177}
178
179fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
180    match v {
181        Some(s) => {
182            *slot = PathBuf::from(s);
183            Ok(())
184        }
185        None => Err("a path"),
186    }
187}
188
189fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
190    match v {
191        Some(s) => {
192            *slot = s.parse()?;
193            Ok(())
194        }
195        _ => Err(PointerWidth::ERROR),
196    }
197}
198
199fn parse_overflow(slot: &mut OverflowMode, v: Option<&str>) -> Result<(), &'static str> {
200    match v {
201        Some(s) => {
202            *slot = s.parse()?;
203            Ok(())
204        }
205        _ => Err(OverflowMode::ERROR),
206    }
207}
208
209fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
210    match v {
211        Some(s) => {
212            *slot = s.parse()?;
213            Ok(())
214        }
215        _ => Err(SmtSolver::ERROR),
216    }
217}
218
219fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
220    match v {
221        Some(s) => {
222            *slot = Some(PathBuf::from(s));
223            Ok(())
224        }
225        None => Err("a path"),
226    }
227}
228
229fn parse_opt_level(slot: &mut Option<Level>, v: Option<&str>) -> Result<(), &'static str> {
230    match v {
231        Some(s) => {
232            *slot = Some(Level::from_str(s).map_err(|_| "invalid level")?);
233            Ok(())
234        }
235        None => Err("a level"),
236    }
237}
238
239fn parse_opt_include(slot: &mut Vec<String>, v: Option<&str>) -> Result<(), &'static str> {
240    if let Some(include) = v {
241        slot.push(include.to_string());
242    }
243    Ok(())
244}