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