Skip to main content

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    /// If `true`, all code will have suggestions disabled.
234    #[arg(
235        long = flux_arg!("no-suggestions"),
236        num_args = 0..=1,
237        default_missing_value = "false"
238    )]
239    pub no_suggestions_default: bool,
240    /// If `true` (the default), attach a note to each failing item with a copy-pasteable command to
241    /// re-run the check on just that item. Only applies when running under `cargo flux`.
242    #[arg(
243        long = flux_arg!("rerun-hint"),
244        num_args = 0..=1,
245        default_missing_value = "true"
246    )]
247    pub rerun_hint: bool,
248}
249
250impl Default for Flags {
251    fn default() -> Self {
252        Self {
253            log_dir: PathBuf::from("./log/"),
254            lean_dir: PathBuf::from("./"),
255            lean_project: "lean_proofs".to_string(),
256            dump_constraint: false,
257            dump_checker_trace: None,
258            dump_fhir: false,
259            dump_rty: false,
260            catch_bugs: false,
261            pointer_width: PointerWidth::default(),
262            include: None,
263            include_trusted: None,
264            include_trusted_impl: None,
265            cache: None,
266            check_overflow: OverflowMode::default(),
267            allow_raw_deref: RawDerefMode::default(),
268            scrape_quals: false,
269            allow_uninterpreted_cast: false,
270            solver: SmtSolver::default(),
271            smt_define_fun: false,
272            annots: false,
273            timings: false,
274            summary: true,
275            verify: false,
276            full_compilation: false,
277            sysroot: None,
278            trusted_default: false,
279            ignore_default: false,
280            lean: LeanMode::default(),
281            no_panic: false,
282            std_extern_specs: false,
283            flux_verbose: false,
284            no_suggestions_default: false,
285            rerun_hint: true,
286        }
287    }
288}
289
290pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
291    let mut flags = Flags::default();
292    let mut includes: Vec<String> = Vec::new();
293    let mut trusteds: Vec<String> = Vec::new();
294    let mut trusted_impls: Vec<String> = Vec::new();
295    for arg in env::args() {
296        let Some((key, value)) = parse_flux_arg(&arg) else { continue };
297
298        let result = match key {
299            "log-dir" => parse_path_buf(&mut flags.log_dir, value),
300            "lean-dir" => parse_path_buf(&mut flags.lean_dir, value),
301            "lean-project" => parse_string(&mut flags.lean_project, value),
302            "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
303            "dump-checker-trace" => parse_opt_level(&mut flags.dump_checker_trace, value),
304            "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
305            "dump-rty" => parse_bool(&mut flags.dump_rty, value),
306            "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
307            "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
308            "check-overflow" => parse_overflow(&mut flags.check_overflow, value),
309            "allow-raw-deref" => parse_raw_deref(&mut flags.allow_raw_deref, value),
310            "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
311            "allow-uninterpreted-cast" => parse_bool(&mut flags.allow_uninterpreted_cast, value),
312            "solver" => parse_solver(&mut flags.solver, value),
313            "smt-define-fun" => parse_bool(&mut flags.smt_define_fun, value),
314            "annots" => parse_bool(&mut flags.annots, value),
315            "timings" => parse_bool(&mut flags.timings, value),
316            "summary" => parse_bool(&mut flags.summary, value),
317            "cache" => parse_opt_path_buf(&mut flags.cache, value),
318            "include" => parse_opt_include(&mut includes, value),
319            "include-trusted" => parse_opt_include(&mut trusteds, value),
320            "include-trusted-impl" => parse_opt_include(&mut trusted_impls, value),
321            "verify" => parse_bool(&mut flags.verify, value),
322            "full-compilation" => parse_bool(&mut flags.full_compilation, value),
323            "sysroot" => parse_opt_path_buf(&mut flags.sysroot, value),
324            "trusted" => parse_bool(&mut flags.trusted_default, value),
325            "ignore" => parse_bool(&mut flags.ignore_default, value),
326            "lean" => parse_lean_mode(&mut flags.lean, value),
327            "no-panic" => parse_bool(&mut flags.no_panic, value),
328            "std-extern-specs" => parse_bool(&mut flags.std_extern_specs, value),
329            "flux-verbose" => parse_bool(&mut flags.flux_verbose, value),
330            "no-suggestions" => parse_bool(&mut flags.no_suggestions_default, value),
331            "rerun-hint" => parse_bool(&mut flags.rerun_hint, value),
332            _ => {
333                eprintln!("error: unknown flux option: `{key}`");
334                process::exit(EXIT_FAILURE);
335            }
336        };
337        if let Err(reason) = result {
338            eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
339            process::exit(1);
340        }
341    }
342    if !includes.is_empty() {
343        let include = IncludePattern::new(includes).unwrap_or_else(|err| {
344            eprintln!("error: invalid include pattern: {err}");
345            process::exit(1);
346        });
347        flags.include = Some(include);
348    }
349    if !trusteds.is_empty() {
350        let trusted = IncludePattern::new(trusteds).unwrap_or_else(|err| {
351            eprintln!("error: invalid trusted pattern: {err}");
352            process::exit(1);
353        });
354        flags.include_trusted = Some(trusted);
355    }
356    if !trusted_impls.is_empty() {
357        let trusted_impl = IncludePattern::new(trusted_impls).unwrap_or_else(|err| {
358            eprintln!("error: invalid trusted-impl pattern: {err}");
359            process::exit(1);
360        });
361        flags.include_trusted_impl = Some(trusted_impl);
362    }
363    flags
364});
365
366pub fn is_flux_arg(arg: &str) -> bool {
367    parse_flux_arg(arg).is_some()
368}
369
370fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
371    let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
372    if arg.is_empty() {
373        return None;
374    }
375    if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
376}
377
378fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
379    match v {
380        Some("y") | Some("yes") | Some("on") | Some("true") | None => {
381            *slot = true;
382            Ok(())
383        }
384        Some("n") | Some("no") | Some("off") | Some("false") => {
385            *slot = false;
386            Ok(())
387        }
388        _ => {
389            Err(
390                "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
391            )
392        }
393    }
394}
395
396fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
397    match v {
398        Some(s) => {
399            *slot = s.to_string();
400            Ok(())
401        }
402        None => Err("expected a string"),
403    }
404}
405
406fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
407    match v {
408        Some(s) => {
409            *slot = PathBuf::from(s);
410            Ok(())
411        }
412        None => Err("expected a path"),
413    }
414}
415
416fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
417    match v {
418        Some(s) => {
419            *slot = s.parse()?;
420            Ok(())
421        }
422        _ => Err(PointerWidth::ERROR),
423    }
424}
425
426fn parse_lean_mode(slot: &mut LeanMode, v: Option<&str>) -> Result<(), &'static str> {
427    match v {
428        Some(s) => {
429            *slot = s.parse()?;
430            Ok(())
431        }
432        _ => Err(LeanMode::ERROR),
433    }
434}
435
436fn parse_overflow(slot: &mut OverflowMode, v: Option<&str>) -> Result<(), &'static str> {
437    match v {
438        Some(s) => {
439            *slot = s.parse()?;
440            Ok(())
441        }
442        _ => Err(OverflowMode::ERROR),
443    }
444}
445
446fn parse_raw_deref(slot: &mut RawDerefMode, v: Option<&str>) -> Result<(), &'static str> {
447    match v {
448        Some(s) => {
449            *slot = s.parse()?;
450            Ok(())
451        }
452        _ => Err(RawDerefMode::ERROR),
453    }
454}
455
456fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
457    match v {
458        Some(s) => {
459            *slot = s.parse()?;
460            Ok(())
461        }
462        _ => Err(SmtSolver::ERROR),
463    }
464}
465
466fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
467    match v {
468        Some(s) => {
469            *slot = Some(PathBuf::from(s));
470            Ok(())
471        }
472        None => Err("expected a path"),
473    }
474}
475
476fn parse_opt_level(slot: &mut Option<Level>, v: Option<&str>) -> Result<(), &'static str> {
477    match v {
478        Some(s) => {
479            *slot = Some(Level::from_str(s).map_err(|_| "invalid level")?);
480            Ok(())
481        }
482        None => Err("a level"),
483    }
484}
485
486fn parse_opt_include(slot: &mut Vec<String>, v: Option<&str>) -> Result<(), &'static str> {
487    if let Some(include) = v {
488        slot.push(include.to_string());
489    }
490    Ok(())
491}
492
493fn panicking_parser(_s: &str) -> Result<(), String> {
494    panic!("Parsing flux args from cli is not yet supported.");
495}
496
497fn default_pointerwidth(_s: &str) -> Result<PointerWidth, String> {
498    Ok(PointerWidth::default())
499}
500
501fn default_overflowmode(_s: &str) -> Result<OverflowMode, String> {
502    Ok(OverflowMode::default())
503}
504
505fn default_rawderefmode(_s: &str) -> Result<RawDerefMode, String> {
506    Ok(RawDerefMode::default())
507}
508
509fn default_smtsolver(_s: &str) -> Result<SmtSolver, String> {
510    Ok(SmtSolver::default())
511}
512
513fn default_leanmode(_s: &str) -> Result<LeanMode, String> {
514    Ok(LeanMode::default())
515}