flux_config/
flags.rs

1use std::{env, path::PathBuf, process, str::FromStr, sync::LazyLock};
2
3use clap::Args;
4pub use toml::Value;
5use tracing::Level;
6
7use crate::{IncludePattern, LeanMode, OverflowMode, PointerWidth, RawDerefMode, SmtSolver};
8
9const FLUX_FLAG_PREFIX: &str = "-F";
10
11macro_rules! flux_arg {
12    ($name:literal) => {
13        concat!("F", $name)
14    };
15}
16
17/// Exit status code used for invalid flags.
18pub const EXIT_FAILURE: i32 = 2;
19
20#[derive(Args)]
21#[command(next_help_heading = "Flux-Specific Flags (Not Yet Supported)")]
22pub struct Flags {
23    /// Sets the directory to dump data. Defaults to `./log/`.
24    #[arg(
25        long = flux_arg!("log-dir"),
26        value_name = "PATH",
27        default_value = "./log/",
28    )]
29    pub log_dir: PathBuf,
30    /// Sets the directory to put all the emitted lean definitions and verification conditions. Defaults to `./`.
31    #[arg(
32        long = flux_arg!("lean-dir"),
33        value_name = "PATH",
34        default_value = "./"
35    )]
36    pub lean_dir: PathBuf,
37    /// Name of the lean project. Defaults to `lean_proofs`.
38    #[arg(
39        long = flux_arg!("lean-project"),
40        value_name = "NAME",
41        default_value = "lean_proofs"
42    )]
43    pub lean_project: String,
44    /// If present, only check files matching the [`IncludePattern`] a glob pattern.
45    #[arg(long = flux_arg!("include"), value_name = "PATTERN", value_parser = panicking_parser)]
46    pub include: Option<IncludePattern>,
47    /// If present, trust items matching [`IncludePattern`]. This implies `-Finclude`
48    #[arg(long = flux_arg!("include-trusted"), value_name = "PATTERN", value_parser = panicking_parser)]
49    pub include_trusted: Option<IncludePattern>,
50    /// If present, trust items matching [`IncludePattern`]. This implies `-Finclude`
51    #[arg(
52        long = flux_arg!("include-trusted-impl"),
53        value_name = "PATTERN",
54        value_parser = panicking_parser
55    )]
56    pub include_trusted_impl: Option<IncludePattern>,
57    /// Set the pointer size (either `32` or `64`), used to determine if an integer cast is lossy
58    /// (default `64`).
59    #[arg(
60        long = flux_arg!("pointer-width"),
61        value_name = "WIDTH",
62        default_value = "64",
63        value_parser = default_pointerwidth
64    )]
65    pub pointer_width: PointerWidth,
66    /// If present switches on query caching and saves the cache in the provided path
67    #[arg(long = flux_arg!("cache"), value_name = "PATH", value_parser = panicking_parser)]
68    pub cache: Option<PathBuf>,
69    /// Compute statistics about number and size of annotations. Dumps file to [`Self::log_dir`]
70    #[arg(
71        long = flux_arg!("annots"),
72        num_args = 0..=1,
73        default_missing_value = "true"
74    )]
75    pub annots: bool,
76    /// Print statistics about time taken to analyze each fuction. Also dumps a file with the raw
77    /// times for each function.
78    #[arg(
79        long = flux_arg!("timings"),
80        num_args = 0..=1,
81        default_missing_value = "true"
82    )]
83    pub timings: bool,
84    /// Print statistics about number of functions checked, trusted, etc.
85    #[arg(
86        long = flux_arg!("summary"),
87        num_args = 0..=1,
88        default_missing_value = "true"
89    )]
90    pub summary: bool,
91    /// Default solver. Either `z3` or `cvc5`.
92    #[arg(
93        long = flux_arg!("solver"),
94        value_name = "SOLVER",
95        default_value = "z3",
96        value_parser = default_smtsolver
97    )]
98    pub solver: SmtSolver,
99    /// Enables qualifier scrapping in fixpoint
100    #[arg(
101        long = flux_arg!("scrape-quals"),
102        num_args = 0..=1,
103        default_missing_value = "true"
104    )]
105    pub scrape_quals: bool,
106    /// Enables uninterpreted casts
107    #[arg(
108        long = flux_arg!("allow-uninterpreted-cast"),
109        num_args = 0..=1,
110        default_missing_value = "true"
111    )]
112    pub allow_uninterpreted_cast: bool,
113    /// Translates _monomorphic_ `defs` functions into SMT `define-fun` instead of inlining them
114    /// away inside `flux`.
115    #[arg(
116        long = flux_arg!("smt-define-fun"),
117        num_args = 0..=1,
118        default_missing_value = "true"
119    )]
120    pub smt_define_fun: bool,
121    /// If `strict` checks for over and underflow on arithmetic integer operations,
122    /// If `lazy` checks for underflow and loses information if possible overflow,
123    /// If `none` (default), it still checks for underflow on unsigned integer subtraction.
124    #[arg(
125        long = flux_arg!("check-overflow"),
126        value_name = "MODE",
127        default_value = "none",
128        value_parser = default_overflowmode
129    )]
130    pub check_overflow: OverflowMode,
131    /// Whether to allow raw pointer dereferences during refinement checking.
132    #[arg(
133        long = flux_arg!("allow-raw-deref"),
134        value_name = "MODE",
135        default_value = "default",
136        value_parser = default_rawderefmode
137    )]
138    pub allow_raw_deref: RawDerefMode,
139    /// Dump constraints generated for each function (debugging)
140    #[arg(
141        long = flux_arg!("dump-constraint"),
142        num_args = 0..=1,
143        default_missing_value = "true"
144    )]
145    pub dump_constraint: bool,
146    /// Saves the checker's trace (debugging)
147    #[arg(long = flux_arg!("dump-checker-trace"), value_name = "LEVEL", value_parser = panicking_parser)]
148    pub dump_checker_trace: Option<tracing::Level>,
149    /// Saves the `fhir` for each item (debugging)
150    #[arg(
151        long = flux_arg!("dump-fhir"),
152        num_args = 0..=1,
153        default_missing_value = "true"
154    )]
155    pub dump_fhir: bool,
156    /// Saves the the `fhir` (debugging)
157    #[arg(
158        long = flux_arg!("dump-rty"),
159        num_args = 0..=1,
160        default_missing_value = "true"
161    )]
162    pub dump_rty: bool,
163    /// Optimistically keeps running flux even after errors are found to get as many errors as possible
164    #[arg(
165        long = flux_arg!("catch-bugs"),
166        num_args = 0..=1,
167        default_missing_value = "true"
168    )]
169    pub catch_bugs: bool,
170    /// Whether verification for the current crate is enabled. If false (the default), `flux-driver`
171    /// will behave exactly like `rustc`. This flag is managed by the `cargo flux` and `flux` binaries,
172    /// so you don't need to mess with it.
173    #[arg(
174        long = flux_arg!("verify"),
175        num_args = 0..=1,
176        default_missing_value = "false"
177    )]
178    pub verify: bool,
179    /// If `true`, produce artifacts after analysis. This flag is managed by `cargo flux`, so you
180    /// don't typically have to set it manually.
181    #[arg(
182        long = flux_arg!("full-compilation"),
183        num_args = 0..=1,
184        default_missing_value = "true"
185    )]
186    pub full_compilation: bool,
187    /// Path to the Flux sysroot directory. If not set, the driver infers it from its own binary location.
188    #[arg(long = flux_arg!("sysroot"), value_name = "PATH", value_parser = panicking_parser)]
189    pub sysroot: Option<PathBuf>,
190    /// 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.
191    #[arg(
192        long = flux_arg!("trusted"),
193        num_args = 0..=1,
194        default_missing_value = "false"
195    )]
196    pub trusted_default: bool,
197    /// 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.
198    #[arg(
199        long = flux_arg!("ignore"),
200        num_args = 0..=1,
201        default_missing_value = "false"
202    )]
203    pub ignore_default: bool,
204    #[arg(
205        long = flux_arg!("lean"),
206        value_name = "MODE",
207        default_value = "default",
208        value_parser = default_leanmode
209    )]
210    pub lean: LeanMode,
211    /// If `true`, every function is implicitly labeled with a `no_panic` by default.
212    #[arg(
213        long = flux_arg!("no-panic"),
214        num_args = 0..=1,
215        default_missing_value = "false"
216    )]
217    pub no_panic: bool,
218    /// If `true`, automatically inject `flux_core` and `flux_alloc` as force externs using paths
219    /// from `sysroot.toml`. Off by default.
220    #[arg(
221        long = flux_arg!("std-extern-specs"),
222        num_args = 0..=1,
223        default_missing_value = "false"
224    )]
225    pub std_extern_specs: bool,
226    /// If `true`, produce more detailed error messages (e.g. condition spans for fold errors).
227    #[arg(
228        long = flux_arg!("flux-verbose"),
229        num_args = 0..=1,
230        default_missing_value = "false"
231    )]
232    pub flux_verbose: bool,
233}
234
235impl Default for Flags {
236    fn default() -> Self {
237        Self {
238            log_dir: PathBuf::from("./log/"),
239            lean_dir: PathBuf::from("./"),
240            lean_project: "lean_proofs".to_string(),
241            dump_constraint: false,
242            dump_checker_trace: None,
243            dump_fhir: false,
244            dump_rty: false,
245            catch_bugs: false,
246            pointer_width: PointerWidth::default(),
247            include: None,
248            include_trusted: None,
249            include_trusted_impl: None,
250            cache: None,
251            check_overflow: OverflowMode::default(),
252            allow_raw_deref: RawDerefMode::default(),
253            scrape_quals: false,
254            allow_uninterpreted_cast: false,
255            solver: SmtSolver::default(),
256            smt_define_fun: false,
257            annots: false,
258            timings: false,
259            summary: true,
260            verify: false,
261            full_compilation: false,
262            sysroot: None,
263            trusted_default: false,
264            ignore_default: false,
265            lean: LeanMode::default(),
266            no_panic: false,
267            std_extern_specs: false,
268            flux_verbose: false,
269        }
270    }
271}
272
273pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
274    let mut flags = Flags::default();
275    let mut includes: Vec<String> = Vec::new();
276    let mut trusteds: Vec<String> = Vec::new();
277    let mut trusted_impls: Vec<String> = Vec::new();
278    for arg in env::args() {
279        let Some((key, value)) = parse_flux_arg(&arg) else { continue };
280
281        let result = match key {
282            "log-dir" => parse_path_buf(&mut flags.log_dir, value),
283            "lean-dir" => parse_path_buf(&mut flags.lean_dir, value),
284            "lean-project" => parse_string(&mut flags.lean_project, value),
285            "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
286            "dump-checker-trace" => parse_opt_level(&mut flags.dump_checker_trace, value),
287            "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
288            "dump-rty" => parse_bool(&mut flags.dump_rty, value),
289            "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
290            "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
291            "check-overflow" => parse_overflow(&mut flags.check_overflow, value),
292            "allow-raw-deref" => parse_raw_deref(&mut flags.allow_raw_deref, value),
293            "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
294            "allow-uninterpreted-cast" => parse_bool(&mut flags.allow_uninterpreted_cast, value),
295            "solver" => parse_solver(&mut flags.solver, value),
296            "smt-define-fun" => parse_bool(&mut flags.smt_define_fun, value),
297            "annots" => parse_bool(&mut flags.annots, value),
298            "timings" => parse_bool(&mut flags.timings, value),
299            "summary" => parse_bool(&mut flags.summary, value),
300            "cache" => parse_opt_path_buf(&mut flags.cache, value),
301            "include" => parse_opt_include(&mut includes, value),
302            "include-trusted" => parse_opt_include(&mut trusteds, value),
303            "include-trusted-impl" => parse_opt_include(&mut trusted_impls, value),
304            "verify" => parse_bool(&mut flags.verify, value),
305            "full-compilation" => parse_bool(&mut flags.full_compilation, value),
306            "sysroot" => parse_opt_path_buf(&mut flags.sysroot, value),
307            "trusted" => parse_bool(&mut flags.trusted_default, value),
308            "ignore" => parse_bool(&mut flags.ignore_default, value),
309            "lean" => parse_lean_mode(&mut flags.lean, value),
310            "no-panic" => parse_bool(&mut flags.no_panic, value),
311            "std-extern-specs" => parse_bool(&mut flags.std_extern_specs, value),
312            "flux-verbose" => parse_bool(&mut flags.flux_verbose, value),
313            _ => {
314                eprintln!("error: unknown flux option: `{key}`");
315                process::exit(EXIT_FAILURE);
316            }
317        };
318        if let Err(reason) = result {
319            eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
320            process::exit(1);
321        }
322    }
323    if !includes.is_empty() {
324        let include = IncludePattern::new(includes).unwrap_or_else(|err| {
325            eprintln!("error: invalid include pattern: {err}");
326            process::exit(1);
327        });
328        flags.include = Some(include);
329    }
330    if !trusteds.is_empty() {
331        let trusted = IncludePattern::new(trusteds).unwrap_or_else(|err| {
332            eprintln!("error: invalid trusted pattern: {err}");
333            process::exit(1);
334        });
335        flags.include_trusted = Some(trusted);
336    }
337    if !trusted_impls.is_empty() {
338        let trusted_impl = IncludePattern::new(trusted_impls).unwrap_or_else(|err| {
339            eprintln!("error: invalid trusted-impl pattern: {err}");
340            process::exit(1);
341        });
342        flags.include_trusted_impl = Some(trusted_impl);
343    }
344    flags
345});
346
347pub fn is_flux_arg(arg: &str) -> bool {
348    parse_flux_arg(arg).is_some()
349}
350
351fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
352    let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
353    if arg.is_empty() {
354        return None;
355    }
356    if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
357}
358
359fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
360    match v {
361        Some("y") | Some("yes") | Some("on") | Some("true") | None => {
362            *slot = true;
363            Ok(())
364        }
365        Some("n") | Some("no") | Some("off") | Some("false") => {
366            *slot = false;
367            Ok(())
368        }
369        _ => {
370            Err(
371                "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
372            )
373        }
374    }
375}
376
377fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
378    match v {
379        Some(s) => {
380            *slot = s.to_string();
381            Ok(())
382        }
383        None => Err("expected a string"),
384    }
385}
386
387fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
388    match v {
389        Some(s) => {
390            *slot = PathBuf::from(s);
391            Ok(())
392        }
393        None => Err("expected a path"),
394    }
395}
396
397fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
398    match v {
399        Some(s) => {
400            *slot = s.parse()?;
401            Ok(())
402        }
403        _ => Err(PointerWidth::ERROR),
404    }
405}
406
407fn parse_lean_mode(slot: &mut LeanMode, v: Option<&str>) -> Result<(), &'static str> {
408    match v {
409        Some(s) => {
410            *slot = s.parse()?;
411            Ok(())
412        }
413        _ => Err(LeanMode::ERROR),
414    }
415}
416
417fn parse_overflow(slot: &mut OverflowMode, v: Option<&str>) -> Result<(), &'static str> {
418    match v {
419        Some(s) => {
420            *slot = s.parse()?;
421            Ok(())
422        }
423        _ => Err(OverflowMode::ERROR),
424    }
425}
426
427fn parse_raw_deref(slot: &mut RawDerefMode, v: Option<&str>) -> Result<(), &'static str> {
428    match v {
429        Some(s) => {
430            *slot = s.parse()?;
431            Ok(())
432        }
433        _ => Err(RawDerefMode::ERROR),
434    }
435}
436
437fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
438    match v {
439        Some(s) => {
440            *slot = s.parse()?;
441            Ok(())
442        }
443        _ => Err(SmtSolver::ERROR),
444    }
445}
446
447fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
448    match v {
449        Some(s) => {
450            *slot = Some(PathBuf::from(s));
451            Ok(())
452        }
453        None => Err("expected a path"),
454    }
455}
456
457fn parse_opt_level(slot: &mut Option<Level>, v: Option<&str>) -> Result<(), &'static str> {
458    match v {
459        Some(s) => {
460            *slot = Some(Level::from_str(s).map_err(|_| "invalid level")?);
461            Ok(())
462        }
463        None => Err("a level"),
464    }
465}
466
467fn parse_opt_include(slot: &mut Vec<String>, v: Option<&str>) -> Result<(), &'static str> {
468    if let Some(include) = v {
469        slot.push(include.to_string());
470    }
471    Ok(())
472}
473
474fn panicking_parser(_s: &str) -> Result<(), String> {
475    panic!("Parsing flux args from cli is not yet supported.");
476}
477
478fn default_pointerwidth(_s: &str) -> Result<PointerWidth, String> {
479    Ok(PointerWidth::default())
480}
481
482fn default_overflowmode(_s: &str) -> Result<OverflowMode, String> {
483    Ok(OverflowMode::default())
484}
485
486fn default_rawderefmode(_s: &str) -> Result<RawDerefMode, String> {
487    Ok(RawDerefMode::default())
488}
489
490fn default_smtsolver(_s: &str) -> Result<SmtSolver, String> {
491    Ok(SmtSolver::default())
492}
493
494fn default_leanmode(_s: &str) -> Result<LeanMode, String> {
495    Ok(LeanMode::default())
496}