1use std::{env, path::PathBuf, process, str::FromStr, sync::LazyLock};
2
3use clap::{ArgMatches, Args, Command, FromArgMatches, parser::ValueSource};
4pub use toml::Value;
5use tracing::Level;
6
7use crate::{
8 IncludePattern, LeanMode, OverflowMode, PointerWidth, RawDerefMode, SmtSolver, UifOps,
9};
10
11const FLUX_FLAG_PREFIX: &str = "-F";
12
13macro_rules! flux_arg {
14 ($name:literal) => {
15 concat!("F", $name)
16 };
17}
18
19pub const EXIT_FAILURE: i32 = 2;
21
22#[derive(Args)]
23#[command(next_help_heading = "Flux-Specific Flags")]
24pub struct Flags {
25 #[arg(
27 long = flux_arg!("log-dir"),
28 value_name = "PATH",
29 default_value = "./log/",
30 )]
31 pub log_dir: PathBuf,
32 #[arg(
34 long = flux_arg!("lean-dir"),
35 value_name = "PATH",
36 default_value = "./"
37 )]
38 pub lean_dir: PathBuf,
39 #[arg(
41 long = flux_arg!("lean-project"),
42 value_name = "NAME",
43 default_value = "lean_proofs"
44 )]
45 pub lean_project: String,
46 #[arg(long = flux_arg!("include"), value_name = "PATTERN")]
48 pub include: Option<IncludePattern>,
49 #[arg(long = flux_arg!("include-trusted"), value_name = "PATTERN")]
51 pub include_trusted: Option<IncludePattern>,
52 #[arg(long = flux_arg!("include-trusted-impl"), value_name = "PATTERN")]
54 pub include_trusted_impl: Option<IncludePattern>,
55 #[arg(
57 long = flux_arg!("pointer-width"),
58 value_name = "WIDTH",
59 default_value = "64"
60 )]
61 pub pointer_width: PointerWidth,
62 #[arg(long = flux_arg!("cache"), value_name = "PATH")]
64 pub cache: Option<PathBuf>,
65 #[arg(
67 long = flux_arg!("annots"),
68 num_args = 0..=1,
69 default_missing_value = "true"
70 )]
71 pub annots: bool,
72 #[arg(
75 long = flux_arg!("timings"),
76 num_args = 0..=1,
77 default_missing_value = "true"
78 )]
79 pub timings: bool,
80 #[arg(
82 long = flux_arg!("summary"),
83 num_args = 0..=1,
84 default_value = "true",
85 default_missing_value = "true"
86 )]
87 pub summary: bool,
88 #[arg(
90 long = flux_arg!("solver"),
91 value_name = "SOLVER",
92 default_value = "z3"
93 )]
94 pub solver: SmtSolver,
95 #[arg(
97 long = flux_arg!("scrape-quals"),
98 num_args = 0..=1,
99 default_missing_value = "true"
100 )]
101 pub scrape_quals: bool,
102 #[arg(long = flux_arg!("fixpoint-timeout"), value_name = "SECONDS")]
105 pub fixpoint_timeout: Option<u64>,
106 #[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 #[arg(
117 long = flux_arg!("check-overflow"),
118 value_name = "MODE",
119 default_value = "none"
120 )]
121 pub check_overflow: OverflowMode,
122 #[arg(
124 long = flux_arg!("allow-raw-deref"),
125 value_name = "MODE",
126 default_value = "none"
127 )]
128 pub allow_raw_deref: RawDerefMode,
129 #[arg(long = flux_arg!("uif-ops"), value_name = "OPS", default_value = "")]
132 pub uif_ops: UifOps,
133 #[arg(
135 long = flux_arg!("dump-constraint"),
136 num_args = 0..=1,
137 default_missing_value = "true"
138 )]
139 pub dump_constraint: bool,
140 #[arg(long = flux_arg!("dump-checker-trace"), value_name = "LEVEL")]
142 pub dump_checker_trace: Option<tracing::Level>,
143 #[arg(
145 long = flux_arg!("dump-fhir"),
146 num_args = 0..=1,
147 default_missing_value = "true"
148 )]
149 pub dump_fhir: bool,
150 #[arg(
152 long = flux_arg!("dump-rty"),
153 num_args = 0..=1,
154 default_missing_value = "true"
155 )]
156 pub dump_rty: bool,
157 #[arg(
159 long = flux_arg!("catch-bugs"),
160 num_args = 0..=1,
161 default_missing_value = "true"
162 )]
163 pub catch_bugs: bool,
164 #[arg(
168 long = flux_arg!("verify"),
169 num_args = 0..=1,
170 default_missing_value = "true"
171 )]
172 pub verify: bool,
173 #[arg(
176 long = flux_arg!("full-compilation"),
177 num_args = 0..=1,
178 default_missing_value = "true"
179 )]
180 pub full_compilation: bool,
181 #[arg(long = flux_arg!("sysroot"), value_name = "PATH")]
183 pub sysroot: Option<PathBuf>,
184 #[arg(
186 long = flux_arg!("trusted"),
187 num_args = 0..=1,
188 default_missing_value = "true"
189 )]
190 pub trusted_default: bool,
191 #[arg(
193 long = flux_arg!("ignore"),
194 num_args = 0..=1,
195 default_missing_value = "true"
196 )]
197 pub ignore_default: bool,
198 #[arg(long = flux_arg!("lean"), value_name = "MODE", default_value = "off")]
199 pub lean: LeanMode,
200 #[arg(
202 long = flux_arg!("no-panic"),
203 num_args = 0..=1,
204 default_missing_value = "true"
205 )]
206 pub no_panic: bool,
207 #[arg(
210 long = flux_arg!("std-extern-specs"),
211 num_args = 0..=1,
212 default_missing_value = "true"
213 )]
214 pub std_extern_specs: bool,
215 #[arg(
217 long = flux_arg!("flux-verbose"),
218 num_args = 0..=1,
219 default_missing_value = "true"
220 )]
221 pub flux_verbose: bool,
222 #[arg(
224 long = flux_arg!("no-suggestions"),
225 num_args = 0..=1,
226 default_missing_value = "true"
227 )]
228 pub no_suggestions_default: bool,
229 #[arg(
232 long = flux_arg!("rerun-hint"),
233 num_args = 0..=1,
234 default_value = "true",
235 default_missing_value = "true"
236 )]
237 pub rerun_hint: bool,
238}
239
240impl Default for Flags {
241 fn default() -> Self {
242 Self {
243 log_dir: PathBuf::from("./log/"),
244 lean_dir: PathBuf::from("./"),
245 lean_project: "lean_proofs".to_string(),
246 dump_constraint: false,
247 dump_checker_trace: None,
248 dump_fhir: false,
249 dump_rty: false,
250 catch_bugs: false,
251 pointer_width: PointerWidth::default(),
252 include: None,
253 include_trusted: None,
254 include_trusted_impl: None,
255 cache: None,
256 check_overflow: OverflowMode::default(),
257 allow_raw_deref: RawDerefMode::default(),
258 uif_ops: UifOps::default(),
259 scrape_quals: false,
260 fixpoint_timeout: None,
261 allow_uninterpreted_cast: false,
262 solver: SmtSolver::default(),
263 annots: false,
264 timings: false,
265 summary: true,
266 verify: false,
267 full_compilation: false,
268 sysroot: None,
269 trusted_default: false,
270 ignore_default: false,
271 lean: LeanMode::default(),
272 no_panic: false,
273 std_extern_specs: false,
274 flux_verbose: false,
275 no_suggestions_default: false,
276 rerun_hint: true,
277 }
278 }
279}
280
281impl Flags {
282 pub fn rustflags(matches: &ArgMatches) -> Vec<String> {
284 let spec = Flags::augment_args(Command::new("flux-flags"));
285 let mut out = Vec::new();
286 for arg in spec.get_arguments() {
287 let id = arg.get_id().as_str();
288 if matches.value_source(id) != Some(ValueSource::CommandLine) {
289 continue;
290 }
291 let Some(long) = arg.get_long() else { continue };
292 match matches.get_raw(id) {
293 Some(values) => {
294 out.extend(values.map(|v| format!("-{long}={}", v.to_string_lossy())))
295 }
296 None => out.push(format!("-{long}")),
297 }
298 }
299 out
300 }
301}
302
303#[derive(Clone, Debug, Default)]
304pub struct FluxFlags(pub Vec<String>);
305
306impl FromArgMatches for FluxFlags {
307 fn from_arg_matches(m: &ArgMatches) -> Result<Self, clap::Error> {
308 Ok(FluxFlags(Flags::rustflags(m)))
309 }
310 fn update_from_arg_matches(&mut self, m: &ArgMatches) -> Result<(), clap::Error> {
311 self.0 = Flags::rustflags(m);
312 Ok(())
313 }
314}
315
316impl Args for FluxFlags {
317 fn augment_args(cmd: Command) -> Command {
318 Flags::augment_args(cmd)
319 }
320 fn augment_args_for_update(cmd: Command) -> Command {
321 Flags::augment_args_for_update(cmd)
322 }
323}
324
325pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
326 let mut flags = Flags::default();
327 let mut includes: Vec<String> = Vec::new();
328 let mut trusteds: Vec<String> = Vec::new();
329 let mut trusted_impls: Vec<String> = Vec::new();
330 for arg in env::args() {
331 let Some((key, value)) = parse_flux_arg(&arg) else { continue };
332
333 let result = match key {
334 "log-dir" => parse_path_buf(&mut flags.log_dir, value),
335 "lean-dir" => parse_path_buf(&mut flags.lean_dir, value),
336 "lean-project" => parse_string(&mut flags.lean_project, value),
337 "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
338 "dump-checker-trace" => parse_opt_level(&mut flags.dump_checker_trace, value),
339 "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
340 "dump-rty" => parse_bool(&mut flags.dump_rty, value),
341 "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
342 "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
343 "check-overflow" => parse_overflow(&mut flags.check_overflow, value),
344 "allow-raw-deref" => parse_raw_deref(&mut flags.allow_raw_deref, value),
345 "uif-ops" => parse_uif_ops(&mut flags.uif_ops, value),
346 "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
347 "fixpoint-timeout" => parse_opt_u64(&mut flags.fixpoint_timeout, value),
348 "allow-uninterpreted-cast" => parse_bool(&mut flags.allow_uninterpreted_cast, value),
349 "solver" => parse_solver(&mut flags.solver, value),
350 "annots" => parse_bool(&mut flags.annots, value),
351 "timings" => parse_bool(&mut flags.timings, value),
352 "summary" => parse_bool(&mut flags.summary, value),
353 "cache" => parse_opt_path_buf(&mut flags.cache, value),
354 "include" => parse_opt_include(&mut includes, value),
355 "include-trusted" => parse_opt_include(&mut trusteds, value),
356 "include-trusted-impl" => parse_opt_include(&mut trusted_impls, value),
357 "verify" => parse_bool(&mut flags.verify, value),
358 "full-compilation" => parse_bool(&mut flags.full_compilation, value),
359 "sysroot" => parse_opt_path_buf(&mut flags.sysroot, value),
360 "trusted" => parse_bool(&mut flags.trusted_default, value),
361 "ignore" => parse_bool(&mut flags.ignore_default, value),
362 "lean" => parse_lean_mode(&mut flags.lean, value),
363 "no-panic" => parse_bool(&mut flags.no_panic, value),
364 "std-extern-specs" => parse_bool(&mut flags.std_extern_specs, value),
365 "flux-verbose" => parse_bool(&mut flags.flux_verbose, value),
366 "no-suggestions" => parse_bool(&mut flags.no_suggestions_default, value),
367 "rerun-hint" => parse_bool(&mut flags.rerun_hint, value),
368 _ => {
369 eprintln!("error: unknown flux option: `{key}`");
370 process::exit(EXIT_FAILURE);
371 }
372 };
373 if let Err(reason) = result {
374 eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
375 process::exit(1);
376 }
377 }
378 if !includes.is_empty() {
379 let include = IncludePattern::new(includes).unwrap_or_else(|err| {
380 eprintln!("error: invalid include pattern: {err}");
381 process::exit(1);
382 });
383 flags.include = Some(include);
384 }
385 if !trusteds.is_empty() {
386 let trusted = IncludePattern::new(trusteds).unwrap_or_else(|err| {
387 eprintln!("error: invalid trusted pattern: {err}");
388 process::exit(1);
389 });
390 flags.include_trusted = Some(trusted);
391 }
392 if !trusted_impls.is_empty() {
393 let trusted_impl = IncludePattern::new(trusted_impls).unwrap_or_else(|err| {
394 eprintln!("error: invalid trusted-impl pattern: {err}");
395 process::exit(1);
396 });
397 flags.include_trusted_impl = Some(trusted_impl);
398 }
399 flags
400});
401
402pub fn is_flux_arg(arg: &str) -> bool {
403 parse_flux_arg(arg).is_some()
404}
405
406fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
407 let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
408 if arg.is_empty() {
409 return None;
410 }
411 if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
412}
413
414fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
415 match v {
416 Some("y") | Some("yes") | Some("on") | Some("true") | None => {
417 *slot = true;
418 Ok(())
419 }
420 Some("n") | Some("no") | Some("off") | Some("false") => {
421 *slot = false;
422 Ok(())
423 }
424 _ => {
425 Err(
426 "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
427 )
428 }
429 }
430}
431
432fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
433 match v {
434 Some(s) => {
435 *slot = s.to_string();
436 Ok(())
437 }
438 None => Err("expected a string"),
439 }
440}
441
442fn parse_opt_u64(slot: &mut Option<u64>, v: Option<&str>) -> Result<(), &'static str> {
443 match v.and_then(|s| s.parse().ok()) {
444 Some(n) => {
445 *slot = Some(n);
446 Ok(())
447 }
448 None => Err("expected a non-negative integer"),
449 }
450}
451
452fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
453 match v {
454 Some(s) => {
455 *slot = PathBuf::from(s);
456 Ok(())
457 }
458 None => Err("expected a path"),
459 }
460}
461
462fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
463 match v {
464 Some(s) => {
465 *slot = s.parse()?;
466 Ok(())
467 }
468 _ => Err(PointerWidth::ERROR),
469 }
470}
471
472fn parse_lean_mode(slot: &mut LeanMode, v: Option<&str>) -> Result<(), &'static str> {
473 match v {
474 Some(s) => {
475 *slot = s.parse()?;
476 Ok(())
477 }
478 _ => Err(LeanMode::ERROR),
479 }
480}
481
482fn parse_overflow(slot: &mut OverflowMode, v: Option<&str>) -> Result<(), &'static str> {
483 match v {
484 Some(s) => {
485 *slot = s.parse()?;
486 Ok(())
487 }
488 _ => Err(OverflowMode::ERROR),
489 }
490}
491
492fn parse_uif_ops(slot: &mut UifOps, v: Option<&str>) -> Result<(), &'static str> {
493 match v {
494 Some(s) => {
495 *slot = s.parse()?;
496 Ok(())
497 }
498 _ => Err(UifOps::ERROR),
499 }
500}
501
502fn parse_raw_deref(slot: &mut RawDerefMode, v: Option<&str>) -> Result<(), &'static str> {
503 match v {
504 Some(s) => {
505 *slot = s.parse()?;
506 Ok(())
507 }
508 _ => Err(RawDerefMode::ERROR),
509 }
510}
511
512fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
513 match v {
514 Some(s) => {
515 *slot = s.parse()?;
516 Ok(())
517 }
518 _ => Err(SmtSolver::ERROR),
519 }
520}
521
522fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
523 match v {
524 Some(s) => {
525 *slot = Some(PathBuf::from(s));
526 Ok(())
527 }
528 None => Err("expected a path"),
529 }
530}
531
532fn parse_opt_level(slot: &mut Option<Level>, v: Option<&str>) -> Result<(), &'static str> {
533 match v {
534 Some(s) => {
535 *slot = Some(Level::from_str(s).map_err(|_| "invalid level")?);
536 Ok(())
537 }
538 None => Err("a level"),
539 }
540}
541
542fn parse_opt_include(slot: &mut Vec<String>, v: Option<&str>) -> Result<(), &'static str> {
543 if let Some(include) = v {
544 slot.push(include.to_string());
545 }
546 Ok(())
547}