1#![feature(if_let_guard)]
2use globset::{Glob, GlobSet, GlobSetBuilder};
3pub use toml::Value;
4use tracing::Level;
5pub mod flags;
6
7use std::{
8 fmt,
9 io::Read,
10 path::{Path, PathBuf},
11 str::FromStr,
12 sync::LazyLock,
13};
14
15use flags::FLAGS;
16use serde::Deserialize;
17
18pub fn dump_checker_trace_info() -> bool {
19 match FLAGS.dump_checker_trace {
20 Some(l) => Level::INFO <= l,
21 None => false,
22 }
23}
24
25pub fn dump_checker_trace() -> Option<Level> {
26 FLAGS.dump_checker_trace
27}
28
29pub fn dump_constraint() -> bool {
30 FLAGS.dump_constraint
31}
32
33pub fn dump_fhir() -> bool {
34 FLAGS.dump_fhir
35}
36
37pub fn dump_rty() -> bool {
38 FLAGS.dump_rty
39}
40
41pub fn pointer_width() -> PointerWidth {
42 FLAGS.pointer_width
43}
44
45pub fn log_dir() -> &'static PathBuf {
46 &FLAGS.log_dir
47}
48
49pub fn lean_dir() -> &'static PathBuf {
50 &FLAGS.lean_dir
51}
52
53pub fn lean_project() -> &'static str {
54 &FLAGS.lean_project
55}
56
57pub fn is_cache_enabled() -> bool {
58 FLAGS.cache.is_some()
59}
60
61pub fn trusted_default() -> bool {
62 FLAGS.trusted_default
63}
64
65pub fn ignore_default() -> bool {
66 FLAGS.ignore_default
67}
68
69pub fn lean() -> LeanMode {
70 FLAGS.lean
71}
72
73pub fn cache_path() -> Option<&'static Path> {
74 FLAGS.cache.as_deref()
75}
76
77pub fn include_pattern() -> Option<&'static IncludePattern> {
78 FLAGS.include.as_ref()
79}
80
81fn check_overflow() -> OverflowMode {
82 FLAGS.check_overflow
83}
84
85pub fn allow_uninterpreted_cast() -> bool {
86 FLAGS.allow_uninterpreted_cast
87}
88
89fn scrape_quals() -> bool {
90 FLAGS.scrape_quals
91}
92
93pub fn no_panic() -> bool {
94 FLAGS.no_panic
95}
96
97pub fn smt_define_fun() -> bool {
98 FLAGS.smt_define_fun
99}
100
101fn solver() -> SmtSolver {
102 FLAGS.solver
103}
104
105pub fn catch_bugs() -> bool {
106 FLAGS.catch_bugs
107}
108
109pub fn annots() -> bool {
110 FLAGS.annots
111}
112
113pub fn timings() -> bool {
114 FLAGS.timings
115}
116
117pub fn verify() -> bool {
118 FLAGS.verify
119}
120
121pub fn summary() -> bool {
122 FLAGS.summary
123}
124
125pub fn full_compilation() -> bool {
126 FLAGS.full_compilation
127}
128
129#[derive(Clone, Debug, Deserialize)]
130#[serde(try_from = "String")]
131pub struct Pos {
132 pub file: String,
133 pub line: usize,
134 pub column: usize,
135}
136
137impl FromStr for Pos {
138 type Err = &'static str;
139
140 fn from_str(s: &str) -> Result<Self, Self::Err> {
141 let s = s.trim();
142 let parts: Vec<&str> = s.split(':').collect();
143 if parts.len() != 3 {
144 return Err("span format should be '<file>:<line>:<column>'");
145 }
146 let file = parts[0].to_string();
147 let line = parts[1]
148 .parse::<usize>()
149 .map_err(|_| "invalid line number")?;
150 let column = parts[2]
151 .parse::<usize>()
152 .map_err(|_| "invalid column number")?;
153 Ok(Pos { file, line, column })
154 }
155}
156
157impl TryFrom<String> for Pos {
158 type Error = &'static str;
159
160 fn try_from(value: String) -> Result<Self, Self::Error> {
161 value.parse()
162 }
163}
164
165impl fmt::Display for IncludePattern {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 write!(f, "[")?;
168 write!(f, "glob:{:?},", self.glob)?;
169 for def in &self.defs {
170 write!(f, "def:{},", def)?;
171 }
172 for pos in &self.spans {
173 write!(f, "span:{}:{}:{}", pos.file, pos.line, pos.column)?;
174 }
175 write!(f, "]")?;
176 Ok(())
177 }
178}
179#[derive(Clone)]
183pub struct IncludePattern {
184 pub glob: GlobSet,
186 pub defs: Vec<String>,
188 pub spans: Vec<Pos>,
190}
191
192impl IncludePattern {
193 fn new(includes: Vec<String>) -> Result<Self, String> {
194 let mut defs = Vec::new();
195 let mut spans = Vec::new();
196 let mut glob = GlobSetBuilder::new();
197 for include in includes {
198 if let Some(suffix) = include.strip_prefix("def:") {
199 defs.push(suffix.to_string());
200 } else if let Some(suffix) = include.strip_prefix("span:") {
201 spans.push(Pos::from_str(suffix)?);
202 } else {
203 let suffix = include.strip_prefix("glob:").unwrap_or(&include);
204 let glob_pattern = Glob::new(suffix.trim()).map_err(|_| "invalid glob pattern")?;
205 glob.add(glob_pattern);
206 }
207 }
208 let glob = glob.build().map_err(|_| "failed to build glob set")?;
209 Ok(IncludePattern { glob, defs, spans })
210 }
211}
212
213#[derive(Clone, Copy, Debug, Deserialize, Default)]
214#[serde(try_from = "String")]
215pub enum LeanMode {
216 #[default]
218 Off,
219 Emit,
221 Check,
223}
224
225impl LeanMode {
226 const ERROR: &'static str = "expected one of `emit`, or `check`";
227
228 pub fn is_emit(self) -> bool {
229 matches!(self, LeanMode::Emit | LeanMode::Check)
230 }
231
232 pub fn is_check(self) -> bool {
233 matches!(self, LeanMode::Check)
234 }
235}
236
237impl FromStr for LeanMode {
238 type Err = &'static str;
239
240 fn from_str(s: &str) -> Result<Self, Self::Err> {
241 let s = s.to_ascii_lowercase();
242 match s.as_str() {
243 "off" => Ok(LeanMode::Off),
244 "emit" => Ok(LeanMode::Emit),
245 "check" => Ok(LeanMode::Check),
246 _ => Err(Self::ERROR),
247 }
248 }
249}
250
251impl TryFrom<String> for LeanMode {
252 type Error = &'static str;
253
254 fn try_from(value: String) -> Result<Self, Self::Error> {
255 value.parse()
256 }
257}
258
259impl fmt::Display for LeanMode {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 match self {
262 LeanMode::Off => write!(f, "off"),
263 LeanMode::Emit => write!(f, "emit"),
264 LeanMode::Check => write!(f, "check"),
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, Deserialize, Default)]
270#[serde(try_from = "String")]
271pub enum OverflowMode {
272 #[default]
274 None,
275 Lazy,
278 StrictUnder,
281 Strict,
284}
285
286impl OverflowMode {
287 const ERROR: &'static str = "expected one of `none`, `lazy`, or `strict`";
288}
289impl FromStr for OverflowMode {
290 type Err = &'static str;
291
292 fn from_str(s: &str) -> Result<Self, Self::Err> {
293 let s = s.to_ascii_lowercase();
294 match s.as_str() {
295 "none" => Ok(OverflowMode::None),
296 "lazy" => Ok(OverflowMode::Lazy),
297 "strict" => Ok(OverflowMode::Strict),
298 "strict-under" => Ok(OverflowMode::StrictUnder),
299 _ => Err(Self::ERROR),
300 }
301 }
302}
303
304impl TryFrom<String> for OverflowMode {
305 type Error = &'static str;
306
307 fn try_from(value: String) -> Result<Self, Self::Error> {
308 value.parse()
309 }
310}
311
312impl fmt::Display for OverflowMode {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 match self {
315 OverflowMode::None => write!(f, "none"),
316 OverflowMode::Lazy => write!(f, "lazy"),
317 OverflowMode::Strict => write!(f, "strict"),
318 OverflowMode::StrictUnder => write!(f, "strict-under"),
319 }
320 }
321}
322
323#[derive(Clone, Copy, Debug, Deserialize, Default)]
324#[serde(try_from = "String")]
325pub enum SmtSolver {
326 #[default]
327 Z3,
328 CVC5,
329}
330
331impl SmtSolver {
332 const ERROR: &'static str = "expected one of `z3` or `cvc5`";
333}
334
335impl FromStr for SmtSolver {
336 type Err = &'static str;
337
338 fn from_str(s: &str) -> Result<Self, Self::Err> {
339 let s = s.to_ascii_lowercase();
340 match s.as_str() {
341 "z3" => Ok(SmtSolver::Z3),
342 "cvc5" => Ok(SmtSolver::CVC5),
343 _ => Err(Self::ERROR),
344 }
345 }
346}
347
348impl TryFrom<String> for SmtSolver {
349 type Error = &'static str;
350
351 fn try_from(value: String) -> Result<Self, Self::Error> {
352 value.parse()
353 }
354}
355
356impl fmt::Display for SmtSolver {
357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 match self {
359 SmtSolver::Z3 => write!(f, "z3"),
360 SmtSolver::CVC5 => write!(f, "cvc5"),
361 }
362 }
363}
364
365#[derive(Clone, Copy, Debug)]
367pub struct InferOpts {
368 pub check_overflow: OverflowMode,
371 pub scrape_quals: bool,
373 pub solver: SmtSolver,
374 pub allow_uninterpreted_cast: bool,
376}
377
378impl From<PartialInferOpts> for InferOpts {
379 fn from(opts: PartialInferOpts) -> Self {
380 InferOpts {
381 check_overflow: opts.check_overflow.unwrap_or_else(check_overflow),
382 scrape_quals: opts.scrape_quals.unwrap_or_else(scrape_quals),
383 solver: opts.solver.unwrap_or_else(solver),
384 allow_uninterpreted_cast: opts
385 .allow_uninterpreted_cast
386 .unwrap_or_else(allow_uninterpreted_cast),
387 }
388 }
389}
390
391#[derive(Clone, Copy, Default, Deserialize, Debug)]
392pub struct PartialInferOpts {
393 pub check_overflow: Option<OverflowMode>,
394 pub scrape_quals: Option<bool>,
395 pub solver: Option<SmtSolver>,
396 pub allow_uninterpreted_cast: Option<bool>,
397}
398
399impl PartialInferOpts {
400 pub fn merge(&mut self, other: &Self) {
401 self.check_overflow = self.check_overflow.or(other.check_overflow);
402 self.allow_uninterpreted_cast = self
403 .allow_uninterpreted_cast
404 .or(other.allow_uninterpreted_cast);
405 self.scrape_quals = self.scrape_quals.or(other.scrape_quals);
406 self.solver = self.solver.or(other.solver);
407 }
408}
409
410#[derive(Copy, Clone, Deserialize, Default)]
411pub enum PointerWidth {
412 W32,
413 #[default]
414 W64,
415}
416
417impl PointerWidth {
418 const ERROR: &str = "pointer width must be 32 or 64";
419
420 pub fn bits(self) -> u64 {
421 match self {
422 PointerWidth::W32 => 32,
423 PointerWidth::W64 => 64,
424 }
425 }
426}
427
428impl FromStr for PointerWidth {
429 type Err = &'static str;
430
431 fn from_str(s: &str) -> Result<Self, Self::Err> {
432 match s {
433 "32" => Ok(PointerWidth::W32),
434 "64" => Ok(PointerWidth::W64),
435 _ => Err(Self::ERROR),
436 }
437 }
438}
439
440fn config_path() -> Option<PathBuf> {
441 let mut path = std::env::current_dir().unwrap();
443 loop {
444 for name in ["flux.toml", ".flux.toml"] {
445 let file = path.join(name);
446 if file.exists() {
447 return Some(file);
448 }
449 }
450 if !path.pop() {
451 return None;
452 }
453 }
454}
455
456pub static CONFIG_FILE: LazyLock<Value> = LazyLock::new(|| {
457 if let Some(path) = config_path() {
458 let mut file = std::fs::File::open(path).unwrap();
459 let mut contents = String::new();
460 file.read_to_string(&mut contents).unwrap();
461 toml::from_str(&contents).unwrap()
462 } else {
463 toml::from_str("").unwrap()
464 }
465});