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 = "DIR")]
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!("suggestions"),
225 num_args = 0..=1,
226 default_value_t = false,
227 default_missing_value = "true"
228 )]
229 pub suggestions: bool,
230 #[arg(
233 long = flux_arg!("rerun-hint"),
234 num_args = 0..=1,
235 default_value = "true",
236 default_missing_value = "true"
237 )]
238 pub rerun_hint: bool,
239}
240
241impl Default for Flags {
242 fn default() -> Self {
243 Self {
244 log_dir: PathBuf::from("./log/"),
245 lean_dir: PathBuf::from("./"),
246 lean_project: "lean_proofs".to_string(),
247 dump_constraint: false,
248 dump_checker_trace: None,
249 dump_fhir: false,
250 dump_rty: false,
251 catch_bugs: false,
252 pointer_width: PointerWidth::default(),
253 include: None,
254 include_trusted: None,
255 include_trusted_impl: None,
256 cache: None,
257 check_overflow: OverflowMode::default(),
258 allow_raw_deref: RawDerefMode::default(),
259 uif_ops: UifOps::default(),
260 scrape_quals: false,
261 fixpoint_timeout: None,
262 allow_uninterpreted_cast: false,
263 solver: SmtSolver::default(),
264 annots: false,
265 timings: false,
266 summary: true,
267 verify: false,
268 full_compilation: false,
269 sysroot: None,
270 trusted_default: false,
271 ignore_default: false,
272 lean: LeanMode::default(),
273 no_panic: false,
274 std_extern_specs: false,
275 flux_verbose: false,
276 suggestions: false,
277 rerun_hint: true,
278 }
279 }
280}
281
282impl Flags {
283 pub fn rustflags(matches: &ArgMatches) -> Vec<String> {
285 let spec = Flags::augment_args(Command::new("flux-flags"));
286 let mut out = Vec::new();
287 for arg in spec.get_arguments() {
288 let id = arg.get_id().as_str();
289 if matches.value_source(id) != Some(ValueSource::CommandLine) {
290 continue;
291 }
292 let Some(long) = arg.get_long() else { continue };
293 match matches.get_raw(id) {
294 Some(values) => {
295 out.extend(values.map(|v| format!("-{long}={}", v.to_string_lossy())));
296 }
297 None => out.push(format!("-{long}")),
298 }
299 }
300 out
301 }
302}
303
304#[derive(Clone, Debug, Default)]
305pub struct FluxFlags(pub Vec<String>);
306
307impl FromArgMatches for FluxFlags {
308 fn from_arg_matches(m: &ArgMatches) -> Result<Self, clap::Error> {
309 Ok(FluxFlags(Flags::rustflags(m)))
310 }
311 fn update_from_arg_matches(&mut self, m: &ArgMatches) -> Result<(), clap::Error> {
312 self.0 = Flags::rustflags(m);
313 Ok(())
314 }
315}
316
317impl Args for FluxFlags {
318 fn augment_args(cmd: Command) -> Command {
319 Flags::augment_args(cmd)
320 }
321 fn augment_args_for_update(cmd: Command) -> Command {
322 Flags::augment_args_for_update(cmd)
323 }
324}
325
326pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
327 let mut flags = Flags::default();
328 let mut includes: Vec<String> = Vec::new();
329 let mut trusteds: Vec<String> = Vec::new();
330 let mut trusted_impls: Vec<String> = Vec::new();
331 for arg in env::args() {
332 let Some((key, value)) = parse_flux_arg(&arg) else { continue };
333
334 let result = match key {
335 "log-dir" => parse_path_buf(&mut flags.log_dir, value),
336 "lean-dir" => parse_path_buf(&mut flags.lean_dir, value),
337 "lean-project" => parse_string(&mut flags.lean_project, value),
338 "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
339 "dump-checker-trace" => parse_opt_level(&mut flags.dump_checker_trace, value),
340 "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
341 "dump-rty" => parse_bool(&mut flags.dump_rty, value),
342 "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
343 "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
344 "check-overflow" => parse_overflow(&mut flags.check_overflow, value),
345 "allow-raw-deref" => parse_raw_deref(&mut flags.allow_raw_deref, value),
346 "uif-ops" => parse_uif_ops(&mut flags.uif_ops, value),
347 "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
348 "fixpoint-timeout" => parse_opt_u64(&mut flags.fixpoint_timeout, value),
349 "allow-uninterpreted-cast" => parse_bool(&mut flags.allow_uninterpreted_cast, value),
350 "solver" => parse_solver(&mut flags.solver, value),
351 "annots" => parse_bool(&mut flags.annots, value),
352 "timings" => parse_bool(&mut flags.timings, value),
353 "summary" => parse_bool(&mut flags.summary, value),
354 "cache" => parse_opt_path_buf(&mut flags.cache, value),
355 "include" => parse_opt_include(&mut includes, value),
356 "include-trusted" => parse_opt_include(&mut trusteds, value),
357 "include-trusted-impl" => parse_opt_include(&mut trusted_impls, value),
358 "verify" => parse_bool(&mut flags.verify, value),
359 "full-compilation" => parse_bool(&mut flags.full_compilation, value),
360 "sysroot" => parse_opt_path_buf(&mut flags.sysroot, value),
361 "trusted" => parse_bool(&mut flags.trusted_default, value),
362 "ignore" => parse_bool(&mut flags.ignore_default, value),
363 "lean" => parse_lean_mode(&mut flags.lean, value),
364 "no-panic" => parse_bool(&mut flags.no_panic, value),
365 "std-extern-specs" => parse_bool(&mut flags.std_extern_specs, value),
366 "flux-verbose" => parse_bool(&mut flags.flux_verbose, value),
367 "suggestions" => parse_bool(&mut flags.suggestions, value),
368 "rerun-hint" => parse_bool(&mut flags.rerun_hint, value),
369 _ => {
370 eprintln!("error: unknown flux option: `{key}`");
371 process::exit(EXIT_FAILURE);
372 }
373 };
374 if let Err(reason) = result {
375 eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
376 process::exit(1);
377 }
378 }
379 if !includes.is_empty() {
380 let include = IncludePattern::new(includes).unwrap_or_else(|err| {
381 eprintln!("error: invalid include pattern: {err}");
382 process::exit(1);
383 });
384 flags.include = Some(include);
385 }
386 if !trusteds.is_empty() {
387 let trusted = IncludePattern::new(trusteds).unwrap_or_else(|err| {
388 eprintln!("error: invalid trusted pattern: {err}");
389 process::exit(1);
390 });
391 flags.include_trusted = Some(trusted);
392 }
393 if !trusted_impls.is_empty() {
394 let trusted_impl = IncludePattern::new(trusted_impls).unwrap_or_else(|err| {
395 eprintln!("error: invalid trusted-impl pattern: {err}");
396 process::exit(1);
397 });
398 flags.include_trusted_impl = Some(trusted_impl);
399 }
400 flags
401});
402
403pub fn is_flux_arg(arg: &str) -> bool {
404 parse_flux_arg(arg).is_some()
405}
406
407fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
408 let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
409 if arg.is_empty() {
410 return None;
411 }
412 if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
413}
414
415fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
416 match v {
417 Some("y") | Some("yes") | Some("on") | Some("true") | None => {
418 *slot = true;
419 Ok(())
420 }
421 Some("n") | Some("no") | Some("off") | Some("false") => {
422 *slot = false;
423 Ok(())
424 }
425 _ => {
426 Err(
427 "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
428 )
429 }
430 }
431}
432
433fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
434 match v {
435 Some(s) => {
436 *slot = s.to_string();
437 Ok(())
438 }
439 None => Err("expected a string"),
440 }
441}
442
443fn parse_opt_u64(slot: &mut Option<u64>, v: Option<&str>) -> Result<(), &'static str> {
444 match v.and_then(|s| s.parse().ok()) {
445 Some(n) => {
446 *slot = Some(n);
447 Ok(())
448 }
449 None => Err("expected a non-negative integer"),
450 }
451}
452
453fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
454 match v {
455 Some(s) => {
456 *slot = PathBuf::from(s);
457 Ok(())
458 }
459 None => Err("expected a path"),
460 }
461}
462
463fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
464 match v {
465 Some(s) => {
466 *slot = s.parse()?;
467 Ok(())
468 }
469 _ => Err(PointerWidth::ERROR),
470 }
471}
472
473fn parse_lean_mode(slot: &mut LeanMode, v: Option<&str>) -> Result<(), &'static str> {
474 match v {
475 Some(s) => {
476 *slot = s.parse()?;
477 Ok(())
478 }
479 _ => Err(LeanMode::ERROR),
480 }
481}
482
483fn parse_overflow(slot: &mut OverflowMode, v: Option<&str>) -> Result<(), &'static str> {
484 match v {
485 Some(s) => {
486 *slot = s.parse()?;
487 Ok(())
488 }
489 _ => Err(OverflowMode::ERROR),
490 }
491}
492
493fn parse_uif_ops(slot: &mut UifOps, v: Option<&str>) -> Result<(), &'static str> {
494 match v {
495 Some(s) => {
496 *slot = s.parse()?;
497 Ok(())
498 }
499 _ => Err(UifOps::ERROR),
500 }
501}
502
503fn parse_raw_deref(slot: &mut RawDerefMode, v: Option<&str>) -> Result<(), &'static str> {
504 match v {
505 Some(s) => {
506 *slot = s.parse()?;
507 Ok(())
508 }
509 _ => Err(RawDerefMode::ERROR),
510 }
511}
512
513fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
514 match v {
515 Some(s) => {
516 *slot = s.parse()?;
517 Ok(())
518 }
519 _ => Err(SmtSolver::ERROR),
520 }
521}
522
523fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
524 match v {
525 Some(s) => {
526 *slot = Some(PathBuf::from(s));
527 Ok(())
528 }
529 None => Err("expected a path"),
530 }
531}
532
533fn parse_opt_level(slot: &mut Option<Level>, v: Option<&str>) -> Result<(), &'static str> {
534 match v {
535 Some(s) => {
536 *slot = Some(Level::from_str(s).map_err(|_| "invalid level")?);
537 Ok(())
538 }
539 None => Err("a level"),
540 }
541}
542
543fn parse_opt_include(slot: &mut Vec<String>, v: Option<&str>) -> Result<(), &'static str> {
544 if let Some(include) = v {
545 slot.push(include.to_string());
546 }
547 Ok(())
548}