1use std::{env, path::PathBuf, process, sync::LazyLock};
2
3use serde::Deserialize;
4pub use toml::Value;
5
6use crate::{PointerWidth, SmtSolver};
7
8const FLUX_FLAG_PREFIX: &str = "-F";
9
10pub const EXIT_FAILURE: i32 = 2;
12
13pub struct Flags {
14 pub log_dir: PathBuf,
16 pub check_def: String,
18 pub check_files: Paths,
20 pub pointer_width: PointerWidth,
23 pub cache: Option<PathBuf>,
25 pub verbose: bool,
26 pub annots: bool,
28 pub timings: bool,
31 pub solver: SmtSolver,
33 pub scrape_quals: bool,
35 pub smt_define_fun: bool,
38 pub check_overflow: bool,
41 pub dump_constraint: bool,
43 pub dump_checker_trace: bool,
45 pub dump_fhir: bool,
47 pub dump_rty: bool,
49 pub dump_mir: bool,
51 pub catch_bugs: bool,
53 pub verify: bool,
57 pub full_compilation: bool,
60 pub trusted_default: bool,
62 pub ignore_default: bool,
64}
65
66impl Default for Flags {
67 fn default() -> Self {
68 Self {
69 log_dir: PathBuf::from("./log/"),
70 dump_constraint: false,
71 dump_checker_trace: false,
72 dump_fhir: false,
73 dump_rty: false,
74 dump_mir: false,
75 catch_bugs: false,
76 pointer_width: PointerWidth::default(),
77 check_def: String::new(),
78 check_files: Paths::default(),
79 cache: None,
80 check_overflow: false,
81 scrape_quals: false,
82 solver: SmtSolver::default(),
83 smt_define_fun: false,
84 verbose: false,
85 annots: false,
86 timings: false,
87 verify: false,
88 full_compilation: false,
89 trusted_default: false,
90 ignore_default: false,
91 }
92 }
93}
94
95pub(crate) static FLAGS: LazyLock<Flags> = LazyLock::new(|| {
96 let mut flags = Flags::default();
97 for arg in env::args() {
98 let Some((key, value)) = parse_flux_arg(&arg) else { continue };
99
100 let result = match key {
101 "log-dir" => parse_path_buf(&mut flags.log_dir, value),
102 "dump-constraint" => parse_bool(&mut flags.dump_constraint, value),
103 "dump-checker-trace" => parse_bool(&mut flags.dump_checker_trace, value),
104 "dump-mir" => parse_bool(&mut flags.dump_mir, value),
105 "dump-fhir" => parse_bool(&mut flags.dump_fhir, value),
106 "dump-rty" => parse_bool(&mut flags.dump_rty, value),
107 "catch-bugs" => parse_bool(&mut flags.catch_bugs, value),
108 "pointer-width" => parse_pointer_width(&mut flags.pointer_width, value),
109 "check-overflow" => parse_bool(&mut flags.check_overflow, value),
110 "scrape-quals" => parse_bool(&mut flags.scrape_quals, value),
111 "solver" => parse_solver(&mut flags.solver, value),
112 "verbose" => parse_bool(&mut flags.verbose, value),
113 "smt-define-fun" => parse_bool(&mut flags.smt_define_fun, value),
114 "annots" => parse_bool(&mut flags.annots, value),
115 "timings" => parse_bool(&mut flags.timings, value),
116 "cache" => parse_opt_path_buf(&mut flags.cache, value),
117 "check-def" => parse_string(&mut flags.check_def, value),
118 "check-files" => parse_check_files(&mut flags.check_files, value),
119 "verify" => parse_bool(&mut flags.verify, value),
120 "full-compilation" => parse_bool(&mut flags.full_compilation, value),
121 "trusted" => parse_bool(&mut flags.trusted_default, value),
122 "ignore" => parse_bool(&mut flags.ignore_default, value),
123 _ => {
124 eprintln!("error: unknown flux option: `{key}`");
125 process::exit(EXIT_FAILURE);
126 }
127 };
128 if let Err(reason) = result {
129 eprintln!("error: incorrect value for flux option `{key}` - `{reason}`");
130 process::exit(1);
131 }
132 }
133 flags
134});
135
136#[derive(Default)]
137pub struct Paths {
138 paths: Option<Vec<PathBuf>>,
139}
140
141impl Paths {
142 pub fn is_checked_file(&self, file: &str) -> bool {
143 self.paths
144 .as_ref()
145 .is_none_or(|p| p.iter().any(|p| p.to_str().unwrap() == file))
146 }
147}
148
149impl<'de> Deserialize<'de> for Paths {
150 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
151 where
152 D: serde::Deserializer<'de>,
153 {
154 let paths: Vec<PathBuf> = String::deserialize(deserializer)?
155 .split(",")
156 .map(str::trim)
157 .filter(|s| !s.is_empty())
158 .map(PathBuf::from)
159 .collect();
160
161 let paths = if paths.is_empty() { None } else { Some(paths) };
162
163 Ok(Paths { paths })
164 }
165}
166
167pub fn is_flux_arg(arg: &str) -> bool {
168 parse_flux_arg(arg).is_some()
169}
170
171fn parse_flux_arg(arg: &str) -> Option<(&str, Option<&str>)> {
172 let arg = arg.strip_prefix(FLUX_FLAG_PREFIX)?;
173 if arg.is_empty() {
174 return None;
175 }
176 if let Some((k, v)) = arg.split_once('=') { Some((k, Some(v))) } else { Some((arg, None)) }
177}
178
179fn parse_bool(slot: &mut bool, v: Option<&str>) -> Result<(), &'static str> {
180 match v {
181 Some("y") | Some("yes") | Some("on") | Some("true") | None => {
182 *slot = true;
183 Ok(())
184 }
185 Some("n") | Some("no") | Some("off") | Some("false") => {
186 *slot = false;
187 Ok(())
188 }
189 _ => {
190 Err(
191 "expected no value or one of `y`, `yes`, `on`, `true`, `n`, `no`, `off`, or `false`",
192 )
193 }
194 }
195}
196
197fn parse_path_buf(slot: &mut PathBuf, v: Option<&str>) -> Result<(), &'static str> {
198 match v {
199 Some(s) => {
200 *slot = PathBuf::from(s);
201 Ok(())
202 }
203 None => Err("a path"),
204 }
205}
206
207fn parse_pointer_width(slot: &mut PointerWidth, v: Option<&str>) -> Result<(), &'static str> {
208 match v {
209 Some(s) => {
210 *slot = s.parse()?;
211 Ok(())
212 }
213 _ => Err(PointerWidth::ERROR),
214 }
215}
216
217fn parse_solver(slot: &mut SmtSolver, v: Option<&str>) -> Result<(), &'static str> {
218 match v {
219 Some(s) => {
220 *slot = s.parse()?;
221 Ok(())
222 }
223 _ => Err(SmtSolver::ERROR),
224 }
225}
226
227fn parse_opt_path_buf(slot: &mut Option<PathBuf>, v: Option<&str>) -> Result<(), &'static str> {
228 match v {
229 Some(s) => {
230 *slot = Some(PathBuf::from(s));
231 Ok(())
232 }
233 None => Err("a path"),
234 }
235}
236
237fn parse_string(slot: &mut String, v: Option<&str>) -> Result<(), &'static str> {
238 match v {
239 Some(s) => {
240 *slot = s.to_string();
241 Ok(())
242 }
243 None => Err("a string"),
244 }
245}
246
247fn parse_check_files(slot: &mut Paths, v: Option<&str>) -> Result<(), &'static str> {
248 match v {
249 Some(s) => {
250 let paths: Vec<PathBuf> = s
251 .split(",")
252 .map(str::trim)
253 .filter(|s| !s.is_empty())
254 .map(PathBuf::from)
255 .collect();
256 *slot = Paths { paths: if paths.is_empty() { None } else { Some(paths) } };
257 Ok(())
258 }
259 None => Err("a comma separated list of paths"),
260 }
261}