1use globset::{Glob, GlobSet, GlobSetBuilder};
2pub use toml::Value;
3use tracing::Level;
4pub mod flags;
5
6use std::{
7 fmt,
8 io::Read,
9 path::{Path, PathBuf},
10 str::FromStr,
11 sync::LazyLock,
12 time::Duration,
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
81pub fn trusted_pattern() -> Option<&'static IncludePattern> {
82 FLAGS.include_trusted.as_ref()
83}
84
85pub fn trusted_impl_pattern() -> Option<&'static IncludePattern> {
86 FLAGS.include_trusted_impl.as_ref()
87}
88
89fn check_overflow() -> OverflowMode {
90 FLAGS.check_overflow
91}
92
93fn uif_ops() -> UifOps {
94 FLAGS.uif_ops
95}
96
97fn allow_raw_deref() -> RawDerefMode {
98 FLAGS.allow_raw_deref
99}
100
101pub fn allow_uninterpreted_cast() -> bool {
102 FLAGS.allow_uninterpreted_cast
103}
104
105fn scrape_quals() -> bool {
106 FLAGS.scrape_quals
107}
108
109pub fn no_panic() -> bool {
110 FLAGS.no_panic
111}
112
113pub fn sysroot() -> Option<PathBuf> {
114 if let Some(p) = FLAGS.sysroot.as_deref() {
115 Some(p.to_path_buf())
116 } else {
117 let exe = std::env::current_exe().ok()?;
118 exe.parent().map(|p| p.to_path_buf())
119 }
120}
121
122fn solver() -> SmtSolver {
123 FLAGS.solver
124}
125
126pub fn catch_bugs() -> bool {
127 FLAGS.catch_bugs
128}
129
130pub fn annots() -> bool {
131 FLAGS.annots
132}
133
134pub fn timings() -> bool {
135 FLAGS.timings
136}
137
138pub fn verify() -> bool {
139 FLAGS.verify
140}
141
142pub fn summary() -> bool {
143 FLAGS.summary
144}
145
146pub fn full_compilation() -> bool {
147 FLAGS.full_compilation
148}
149
150pub fn std_extern_specs() -> bool {
151 FLAGS.std_extern_specs
152}
153
154pub fn verbose() -> bool {
155 FLAGS.flux_verbose
156}
157
158pub fn no_suggestions_default() -> bool {
159 FLAGS.no_suggestions_default
160}
161
162pub fn rerun_hint() -> bool {
163 FLAGS.rerun_hint
164}
165
166pub fn fixpoint_timeout() -> Option<Duration> {
167 FLAGS.fixpoint_timeout.map(Duration::from_secs)
168}
169
170pub fn inside_cargo() -> bool {
173 std::env::var_os("FLUX_CARGO").is_some()
174}
175
176#[derive(Clone, Debug, Deserialize)]
177#[serde(try_from = "String")]
178pub struct Pos {
179 pub file: String,
180 pub line: usize,
181 pub column: usize,
182}
183
184impl FromStr for Pos {
185 type Err = &'static str;
186
187 fn from_str(s: &str) -> Result<Self, Self::Err> {
188 let s = s.trim();
189 let parts: Vec<&str> = s.split(':').collect();
190 if parts.len() != 3 {
191 return Err("span format should be '<file>:<line>:<column>'");
192 }
193 let file = parts[0].to_string();
194 let line = parts[1]
195 .parse::<usize>()
196 .map_err(|_| "invalid line number")?;
197 let column = parts[2]
198 .parse::<usize>()
199 .map_err(|_| "invalid column number")?;
200 Ok(Pos { file, line, column })
201 }
202}
203
204impl TryFrom<String> for Pos {
205 type Error = &'static str;
206
207 fn try_from(value: String) -> Result<Self, Self::Error> {
208 value.parse()
209 }
210}
211
212impl fmt::Display for IncludePattern {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 write!(f, "[")?;
215 write!(f, "glob:{:?},", self.glob)?;
216 for def in &self.defs {
217 write!(f, "def:{},", def)?;
218 }
219 for pos in &self.spans {
220 write!(f, "span:{}:{}:{}", pos.file, pos.line, pos.column)?;
221 }
222 write!(f, "]")?;
223 Ok(())
224 }
225}
226#[derive(Clone)]
230pub struct IncludePattern {
231 pub glob: GlobSet,
233 pub defs: Vec<String>,
235 pub spans: Vec<Pos>,
237}
238
239impl IncludePattern {
240 fn new(includes: Vec<String>) -> Result<Self, String> {
241 let mut defs = Vec::new();
242 let mut spans = Vec::new();
243 let mut glob = GlobSetBuilder::new();
244 for include in includes {
245 if let Some(suffix) = include.strip_prefix("def:") {
246 defs.push(suffix.to_string());
247 } else if let Some(suffix) = include.strip_prefix("span:") {
248 spans.push(Pos::from_str(suffix)?);
249 } else {
250 let suffix = include.strip_prefix("glob:").unwrap_or(&include);
251 let glob_pattern = Glob::new(suffix.trim()).map_err(|_| "invalid glob pattern")?;
252 glob.add(glob_pattern);
253 }
254 }
255 let glob = glob.build().map_err(|_| "failed to build glob set")?;
256 Ok(IncludePattern { glob, defs, spans })
257 }
258}
259
260impl FromStr for IncludePattern {
261 type Err = String;
262
263 fn from_str(s: &str) -> Result<Self, Self::Err> {
264 Self::new(vec![s.to_string()])
265 }
266}
267
268#[derive(Clone, Copy, Debug, Deserialize, Default)]
269#[serde(try_from = "String")]
270pub enum LeanMode {
271 #[default]
273 Off,
274 Emit,
276 Check,
278}
279
280impl LeanMode {
281 const ERROR: &'static str = "expected one of `emit`, or `check`";
282
283 pub fn is_emit(self) -> bool {
284 matches!(self, LeanMode::Emit)
285 }
286
287 pub fn is_check(self) -> bool {
288 matches!(self, LeanMode::Check)
289 }
290}
291
292impl FromStr for LeanMode {
293 type Err = &'static str;
294
295 fn from_str(s: &str) -> Result<Self, Self::Err> {
296 let s = s.to_ascii_lowercase();
297 match s.as_str() {
298 "off" => Ok(LeanMode::Off),
299 "emit" => Ok(LeanMode::Emit),
300 "check" => Ok(LeanMode::Check),
301 _ => Err(Self::ERROR),
302 }
303 }
304}
305
306impl TryFrom<String> for LeanMode {
307 type Error = &'static str;
308
309 fn try_from(value: String) -> Result<Self, Self::Error> {
310 value.parse()
311 }
312}
313
314impl fmt::Display for LeanMode {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 match self {
317 LeanMode::Off => write!(f, "off"),
318 LeanMode::Emit => write!(f, "emit"),
319 LeanMode::Check => write!(f, "check"),
320 }
321 }
322}
323
324#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
327pub enum UifOp {
328 Add,
329 Sub,
330 Mul,
331 Div,
332 Mod,
333 BitAnd,
334 BitOr,
335 BitXor,
336 BitShl,
337 BitShr,
338}
339
340impl UifOp {
341 const TOKENS: [(&'static str, UifOp); 10] = [
343 ("+", UifOp::Add),
344 ("-", UifOp::Sub),
345 ("*", UifOp::Mul),
346 ("/", UifOp::Div),
347 ("%", UifOp::Mod),
348 ("&", UifOp::BitAnd),
349 ("|", UifOp::BitOr),
350 ("^", UifOp::BitXor),
351 ("<<", UifOp::BitShl),
352 (">>", UifOp::BitShr),
353 ];
354
355 pub fn from_token(token: &str) -> Option<UifOp> {
357 Self::TOKENS
358 .iter()
359 .find_map(|(tok, op)| (*tok == token).then_some(*op))
360 }
361
362 fn token(self) -> &'static str {
364 match self {
365 UifOp::Add => "+",
366 UifOp::Sub => "-",
367 UifOp::Mul => "*",
368 UifOp::Div => "/",
369 UifOp::Mod => "%",
370 UifOp::BitAnd => "&",
371 UifOp::BitOr => "|",
372 UifOp::BitXor => "^",
373 UifOp::BitShl => "<<",
374 UifOp::BitShr => ">>",
375 }
376 }
377
378 fn bit(self) -> u16 {
379 1 << (self as u16)
380 }
381}
382
383#[derive(Clone, Copy, PartialEq, Eq, Default, Deserialize)]
386#[serde(try_from = "String")]
387pub struct UifOps(u16);
388
389impl UifOps {
390 const ERROR: &'static str =
391 "expected a comma separated list of `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `<<`, or `>>`";
392
393 pub fn contains(self, op: UifOp) -> bool {
394 self.0 & op.bit() != 0
395 }
396
397 pub fn is_empty(self) -> bool {
398 self.0 == 0
399 }
400
401 pub fn insert(&mut self, op: UifOp) {
402 self.0 |= op.bit();
403 }
404
405 fn iter(self) -> impl Iterator<Item = UifOp> {
406 UifOp::TOKENS
407 .into_iter()
408 .map(|(_, op)| op)
409 .filter(move |op| self.contains(*op))
410 }
411}
412
413impl FromStr for UifOps {
414 type Err = &'static str;
415
416 fn from_str(s: &str) -> Result<Self, Self::Err> {
417 let mut ops = UifOps::default();
418 let tokens = s
419 .split(',')
420 .map(str::trim)
421 .filter(|token| !token.is_empty());
422 for token in tokens {
423 let op = UifOp::from_token(token).ok_or(Self::ERROR)?;
424 ops.insert(op);
425 }
426 Ok(ops)
427 }
428}
429
430impl TryFrom<String> for UifOps {
431 type Error = &'static str;
432
433 fn try_from(value: String) -> Result<Self, Self::Error> {
434 value.parse()
435 }
436}
437
438impl fmt::Display for UifOps {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 let tokens: Vec<_> = self.iter().map(UifOp::token).collect();
441 write!(f, "{}", tokens.join(","))
442 }
443}
444
445impl fmt::Debug for UifOps {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 write!(f, "UifOps({self})")
448 }
449}
450
451#[derive(Clone, Copy, Debug, Deserialize, Default)]
452#[serde(try_from = "String")]
453pub enum OverflowMode {
454 #[default]
456 None,
457 Lazy,
460 StrictUnder,
463 Strict,
466}
467
468impl OverflowMode {
469 const ERROR: &'static str = "expected one of `none`, `lazy`, or `strict`";
470}
471impl FromStr for OverflowMode {
472 type Err = &'static str;
473
474 fn from_str(s: &str) -> Result<Self, Self::Err> {
475 let s = s.to_ascii_lowercase();
476 match s.as_str() {
477 "none" => Ok(OverflowMode::None),
478 "lazy" => Ok(OverflowMode::Lazy),
479 "strict" => Ok(OverflowMode::Strict),
480 "strict-under" => Ok(OverflowMode::StrictUnder),
481 _ => Err(Self::ERROR),
482 }
483 }
484}
485
486impl TryFrom<String> for OverflowMode {
487 type Error = &'static str;
488
489 fn try_from(value: String) -> Result<Self, Self::Error> {
490 value.parse()
491 }
492}
493
494impl fmt::Display for OverflowMode {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 match self {
497 OverflowMode::None => write!(f, "none"),
498 OverflowMode::Lazy => write!(f, "lazy"),
499 OverflowMode::Strict => write!(f, "strict"),
500 OverflowMode::StrictUnder => write!(f, "strict-under"),
501 }
502 }
503}
504
505#[derive(Clone, Copy, Debug, Deserialize, Default)]
506#[serde(try_from = "String")]
507pub enum RawDerefMode {
508 #[default]
510 None,
511 Ok,
513}
514
515impl RawDerefMode {
516 const ERROR: &'static str = "expected one of `none` or `ok`";
517}
518
519impl FromStr for RawDerefMode {
520 type Err = &'static str;
521
522 fn from_str(s: &str) -> Result<Self, Self::Err> {
523 let s = s.to_ascii_lowercase();
524 match s.as_str() {
525 "none" => Ok(RawDerefMode::None),
526 "ok" => Ok(RawDerefMode::Ok),
527 _ => Err(Self::ERROR),
528 }
529 }
530}
531
532impl TryFrom<String> for RawDerefMode {
533 type Error = &'static str;
534
535 fn try_from(value: String) -> Result<Self, Self::Error> {
536 value.parse()
537 }
538}
539
540impl fmt::Display for RawDerefMode {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542 match self {
543 RawDerefMode::None => write!(f, "none"),
544 RawDerefMode::Ok => write!(f, "ok"),
545 }
546 }
547}
548
549#[derive(Clone, Copy, Debug, Deserialize, Default)]
550#[serde(try_from = "String")]
551pub enum SmtSolver {
552 #[default]
553 Z3,
554 CVC5,
555}
556
557impl SmtSolver {
558 const ERROR: &'static str = "expected one of `z3` or `cvc5`";
559}
560
561impl FromStr for SmtSolver {
562 type Err = &'static str;
563
564 fn from_str(s: &str) -> Result<Self, Self::Err> {
565 let s = s.to_ascii_lowercase();
566 match s.as_str() {
567 "z3" => Ok(SmtSolver::Z3),
568 "cvc5" => Ok(SmtSolver::CVC5),
569 _ => Err(Self::ERROR),
570 }
571 }
572}
573
574impl TryFrom<String> for SmtSolver {
575 type Error = &'static str;
576
577 fn try_from(value: String) -> Result<Self, Self::Error> {
578 value.parse()
579 }
580}
581
582impl fmt::Display for SmtSolver {
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 match self {
585 SmtSolver::Z3 => write!(f, "z3"),
586 SmtSolver::CVC5 => write!(f, "cvc5"),
587 }
588 }
589}
590
591#[derive(Clone, Copy, Debug)]
593pub struct InferOpts {
594 pub check_overflow: OverflowMode,
597 pub scrape_quals: bool,
599 pub solver: SmtSolver,
600 pub allow_uninterpreted_cast: bool,
602 pub allow_raw_deref: RawDerefMode,
604 pub uif_ops: UifOps,
606}
607
608impl From<PartialInferOpts> for InferOpts {
609 fn from(opts: PartialInferOpts) -> Self {
610 InferOpts {
611 check_overflow: opts.check_overflow.unwrap_or_else(check_overflow),
612 scrape_quals: opts.scrape_quals.unwrap_or_else(scrape_quals),
613 solver: opts.solver.unwrap_or_else(solver),
614 allow_uninterpreted_cast: opts
615 .allow_uninterpreted_cast
616 .unwrap_or_else(allow_uninterpreted_cast),
617 allow_raw_deref: opts.allow_raw_deref.unwrap_or_else(allow_raw_deref),
618 uif_ops: opts.uif_ops.unwrap_or_else(uif_ops),
619 }
620 }
621}
622
623#[derive(Clone, Copy, Default, Deserialize, Debug)]
624pub struct PartialInferOpts {
625 pub check_overflow: Option<OverflowMode>,
626 pub scrape_quals: Option<bool>,
627 pub solver: Option<SmtSolver>,
628 pub allow_uninterpreted_cast: Option<bool>,
629 pub allow_raw_deref: Option<RawDerefMode>,
630 pub uif_ops: Option<UifOps>,
631}
632
633impl PartialInferOpts {
634 pub fn merge(&mut self, other: &Self) {
635 self.check_overflow = self.check_overflow.or(other.check_overflow);
636 self.allow_uninterpreted_cast = self
637 .allow_uninterpreted_cast
638 .or(other.allow_uninterpreted_cast);
639 self.scrape_quals = self.scrape_quals.or(other.scrape_quals);
640 self.solver = self.solver.or(other.solver);
641 self.allow_raw_deref = self.allow_raw_deref.or(other.allow_raw_deref);
642 self.uif_ops = self.uif_ops.or(other.uif_ops);
643 }
644}
645
646#[derive(Copy, Clone, Deserialize, Default)]
647pub enum PointerWidth {
648 W32,
649 #[default]
650 W64,
651}
652
653impl PointerWidth {
654 const ERROR: &str = "pointer width must be 32 or 64";
655
656 pub fn bits(self) -> u64 {
657 match self {
658 PointerWidth::W32 => 32,
659 PointerWidth::W64 => 64,
660 }
661 }
662}
663
664impl FromStr for PointerWidth {
665 type Err = &'static str;
666
667 fn from_str(s: &str) -> Result<Self, Self::Err> {
668 match s {
669 "32" => Ok(PointerWidth::W32),
670 "64" => Ok(PointerWidth::W64),
671 _ => Err(Self::ERROR),
672 }
673 }
674}
675
676fn config_path() -> Option<PathBuf> {
677 let mut path = std::env::current_dir().unwrap();
679 loop {
680 for name in ["flux.toml", ".flux.toml"] {
681 let file = path.join(name);
682 if file.exists() {
683 return Some(file);
684 }
685 }
686 if !path.pop() {
687 return None;
688 }
689 }
690}
691
692pub static CONFIG_FILE: LazyLock<Value> = LazyLock::new(|| {
693 if let Some(path) = config_path() {
694 let mut file = std::fs::File::open(path).unwrap();
695 let mut contents = String::new();
696 file.read_to_string(&mut contents).unwrap();
697 toml::from_str(&contents).unwrap()
698 } else {
699 toml::from_str("").unwrap()
700 }
701});