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, LeanMode, OverflowMode, PointerWidth, RawDerefMode, 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    /// Sets the directory to put all the emitted lean definitions and verification conditions. Defaults to `./`.
17    pub lean_dir: PathBuf,
18    /// Name of the lean project. Defaults to `lean_proofs`.
19    pub lean_project: String,
20    /// If present, only check files matching the [`IncludePattern`] a glob pattern.
21    pub include: Option<IncludePattern>,
22    /// If present, trust items matching [`IncludePattern`]. This implies `-Finclude`
23    pub include_trusted: Option<IncludePattern>,
24    /// If present, trust items matching [`IncludePattern`]. This implies `-Finclude`
25    pub include_trusted_impl: Option<IncludePattern>,
26    /// Set the pointer size (either `32` or `64`), used to determine if an integer cast is lossy
27    /// (default `64`).
28    pub pointer_width: PointerWidth,
29    /// If present switches on query caching and saves the cache in the provided path
30    pub cache: Option<PathBuf>,
31    /// Compute statistics about number and size of annotations. Dumps file to [`Self::log_dir`]
32    pub annots: bool,
33    /// Print statistics about time taken to analyze each fuction. Also dumps a file with the raw
34    /// times for each function.
35    pub timings: bool,
36    /// Print statistics about number of functions checked, trusted, etc.
37    pub summary: bool,
38    /// Default solver. Either `z3` or `cvc5`.
39    pub solver: SmtSolver,
40    /// Enables qualifier scrapping in fixpoint
41    pub scrape_quals: bool,
42    /// Enables uninterpreted casts
43    pub allow_uninterpreted_cast: bool,
44    /// Translates _monomorphic_ `defs` functions into SMT `define-fun` instead of inlining them
45    /// away inside `flux`.
46    pub smt_define_fun: bool,
47    /// If `strict` checks for over and underflow on arithmetic integer operations,
48    /// If `lazy` checks for underflow and loses information if possible overflow,
49    /// If `none` (default), it still checks for underflow on unsigned integer subtraction.
50    pub check_overflow: OverflowMode,
51    /// Whether to allow raw pointer dereferences during refinement checking.
52    pub allow_raw_deref: RawDerefMode,
53    /// Dump constraints generated for each function (debugging)
54    pub dump_constraint: bool,
55    /// Saves the checker's trace (debugging)
56    pub dump_checker_trace: Option<tracing::Level>,
57    /// Saves the `fhir` for each item (debugging)
58    pub dump_fhir: bool,
59    /// Saves the the `fhir` (debugging)
60    pub dump_rty: bool,
61    /// Optimistically keeps running flux even after errors are found to get as many errors as possible
62    pub catch_bugs: bool,
63    /// Whether verification for the current crate is enabled. If false (the default), `flux-driver`
64    /// will behave exactly like `rustc`. This flag is managed by the `cargo flux` and `flux` binaries,
65    /// so you don't need to mess with it.
66    pub verify: bool,
67    /// If `true`, produce artifacts after analysis. This flag is managed by `cargo flux`, so you
68    /// don't typically have to set it manually.
69    pub full_compilation: bool,
70    /// 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.
71    pub trusted_default: bool,
72    /// 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.
73    pub ignore_default: bool,
74    pub lean: LeanMode,
75    /// If `true`, every function is implicitly labeled with a `no_panic` by default.
76    pub no_panic: bool,
77}
78
79impl Default for Flags {
80    fn default() -> Self {
81        Self {
82            log_dir: PathBuf::from("./log/"),
83            lean_dir: PathBuf::from("./"),
84            lean_project: "lean_proofs".to_string(),
85            dump_constraint: false,
86            dump_checker_trace: None,
87            dump_fhir: false,
88            dump_rty: false,
89            catch_bugs: false,
90            pointer_width: PointerWidth::default(),
91            include: None,
92            include_trusted: None,
93            include_trusted_impl: None,
94            cache: None,
95            check_overflow: OverflowMode::default(),
96            allow_raw_deref: RawDerefMode::default(),
97            scrape_quals: false,
98            allow_uninterpreted_cast: false,
99            solver: SmtSolver::default(),
100            smt_define_fun: false,
101            annots: false,
102            timings: false,
103            summary: true,
104            verify: false,
105            full_compilation: false,
106            trusted_default: false,
107            ignore_default: false,
108            lean: LeanMode::default(),
109            no_panic: false,
110        }
111    }
112}
113
114pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
115    let mut flags = Flags::default();
116    let mut includes: Vec<String> = Vec::new();
117    let mut trusteds: Vec<String> = Vec::new();
118    let mut trusted_impls: Vec<String> = Vec::new();
119    for arg in env::args() {
120        let Some((key, value)) = parse_flux_arg(&arg) else { continue };
121
122        let result = match key {
123            "log-dir" => parse_path_buf(&mut flags.log_dir, value),
124            "lean-dir" => parse_path_buf(&mut flags.lean_dir, value),
125            "lean-project" => parse_string(&mut flags.lean_project, value),
126            "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
127            "dump-checker-trace" => parse_opt_level(&mut flags.dump_checker_trace, value),
128            "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
129            "dump-rty" => parse_bool(&mut flags.dump_rty, value),
130            "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
131            "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
132            "check-overflow" => parse_overflow(&mut flags.check_overflow, value),
133            "allow-raw-deref" => parse_raw_deref(&mut flags.allow_raw_deref, value),
134            "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
135            "allow-uninterpreted-cast" => parse_bool(&mut flags.allow_uninterpreted_cast, value),
136            "solver" => parse_solver(&mut flags.solver, value),
137            "smt-define-fun" => parse_bool(&mut flags.smt_define_fun, value),
138            "annots" => parse_bool(&mut flags.annots, value),
139            "timings" => parse_bool(&mut flags.timings, value),
140            "summary" => parse_bool(&mut flags.summary, value),
141            "cache" => parse_opt_path_buf(&mut flags.cache, value),
142            "include" => parse_opt_include(&mut includes, value),
143            "include-trusted" => parse_opt_include(&mut trusteds, value),
144            "include-trusted-impl" => parse_opt_include(&mut trusted_impls, value),
145            "verify" => parse_bool(&mut flags.verify, value),
146            "full-compilation" => parse_bool(&mut flags.full_compilation, value),
147            "trusted" => parse_bool(&mut flags.trusted_default, value),
148            "ignore" => parse_bool(&mut flags.ignore_default, value),
149            "lean" => parse_lean_mode(&mut flags.lean, value),
150            "no-panic" => parse_bool(&mut flags.no_panic, value),
151            _ => {
152                eprintln!("error: unknown flux option: `{key}`");
153                process::exit(EXIT_FAILURE);
154            }
155        };
156        if let Err(reason) = result {
157            eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
158            process::exit(1);
159        }
160    }
161    if !includes.is_empty() {
162        let include = IncludePattern::new(includes).unwrap_or_else(|err| {
163            eprintln!("error: invalid include pattern: {err}");
164            process::exit(1);
165        });
166        flags.include = Some(include);
167    }
168    if !trusteds.is_empty() {
169        let trusted = IncludePattern::new(trusteds).unwrap_or_else(|err| {
170            eprintln!("error: invalid trusted pattern: {err}");
171            process::exit(1);
172        });
173        flags.include_trusted = Some(trusted);
174    }
175    if !trusted_impls.is_empty() {
176        let trusted_impl = IncludePattern::new(trusted_impls).unwrap_or_else(|err| {
177            eprintln!("error: invalid trusted-impl pattern: {err}");
178            process::exit(1);
179        });
180        flags.include_trusted_impl = Some(trusted_impl);
181    }
182    flags
183});
184
185pub fn is_flux_arg(arg: &str) -> bool {
186    parse_flux_arg(arg).is_some()
187}
188
189fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
190    let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
191    if arg.is_empty() {
192        return None;
193    }
194    if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
195}
196
197fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
198    match v {
199        Some("y") | Some("yes") | Some("on") | Some("true") | None => {
200            *slot = true;
201            Ok(())
202        }
203        Some("n") | Some("no") | Some("off") | Some("false") => {
204            *slot = false;
205            Ok(())
206        }
207        _ => {
208            Err(
209                "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
210            )
211        }
212    }
213}
214
215fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
216    match v {
217        Some(s) => {
218            *slot = s.to_string();
219            Ok(())
220        }
221        None => Err("a string"),
222    }
223}
224
225fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
226    match v {
227        Some(s) => {
228            *slot = PathBuf::from(s);
229            Ok(())
230        }
231        None => Err("a path"),
232    }
233}
234
235fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
236    match v {
237        Some(s) => {
238            *slot = s.parse()?;
239            Ok(())
240        }
241        _ => Err(PointerWidth::ERROR),
242    }
243}
244
245fn parse_lean_mode(slot: &mut LeanMode, v: Option<&str>) -> Result<(), &'static str> {
246    match v {
247        Some(s) => {
248            *slot = s.parse()?;
249            Ok(())
250        }
251        _ => Err(LeanMode::ERROR),
252    }
253}
254
255fn parse_overflow(slot: &mut OverflowMode, v: Option<&str>) -> Result<(), &'static str> {
256    match v {
257        Some(s) => {
258            *slot = s.parse()?;
259            Ok(())
260        }
261        _ => Err(OverflowMode::ERROR),
262    }
263}
264
265fn parse_raw_deref(slot: &mut RawDerefMode, v: Option<&str>) -> Result<(), &'static str> {
266    match v {
267        Some(s) => {
268            *slot = s.parse()?;
269            Ok(())
270        }
271        _ => Err(RawDerefMode::ERROR),
272    }
273}
274
275fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
276    match v {
277        Some(s) => {
278            *slot = s.parse()?;
279            Ok(())
280        }
281        _ => Err(SmtSolver::ERROR),
282    }
283}
284
285fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
286    match v {
287        Some(s) => {
288            *slot = Some(PathBuf::from(s));
289            Ok(())
290        }
291        None => Err("a path"),
292    }
293}
294
295fn parse_opt_level(slot: &mut Option<Level>, v: Option<&str>) -> Result<(), &'static str> {
296    match v {
297        Some(s) => {
298            *slot = Some(Level::from_str(s).map_err(|_| "invalid level")?);
299            Ok(())
300        }
301        None => Err("a level"),
302    }
303}
304
305fn parse_opt_include(slot: &mut Vec<String>, v: Option<&str>) -> Result<(), &'static str> {
306    if let Some(include) = v {
307        slot.push(include.to_string());
308    }
309    Ok(())
310}