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() -> Option<Level> {
19 FLAGS.dump_checker_trace
20}
21
22pub fn dump_constraint() -> bool {
23 FLAGS.dump_constraint
24}
25
26pub fn dump_fhir() -> bool {
27 FLAGS.dump_fhir
28}
29
30pub fn dump_rty() -> bool {
31 FLAGS.dump_rty
32}
33
34pub fn pointer_width() -> PointerWidth {
35 FLAGS.pointer_width
36}
37
38pub fn log_dir() -> &'static PathBuf {
39 &FLAGS.log_dir
40}
41
42pub fn is_cache_enabled() -> bool {
43 FLAGS.cache.is_some()
44}
45
46pub fn trusted_default() -> bool {
47 FLAGS.trusted_default
48}
49
50pub fn ignore_default() -> bool {
51 FLAGS.ignore_default
52}
53
54pub fn cache_path() -> Option<&'static Path> {
55 FLAGS.cache.as_deref()
56}
57
58pub fn include_pattern() -> Option<&'static IncludePattern> {
59 FLAGS.include.as_ref()
60}
61
62fn check_overflow() -> OverflowMode {
63 FLAGS.check_overflow
64}
65
66pub fn allow_uninterpreted_cast() -> bool {
67 FLAGS.allow_uninterpreted_cast
68}
69
70fn scrape_quals() -> bool {
71 FLAGS.scrape_quals
72}
73
74pub fn smt_define_fun() -> bool {
75 FLAGS.smt_define_fun
76}
77
78pub fn verbose() -> bool {
79 FLAGS.verbose
80}
81
82fn solver() -> SmtSolver {
83 FLAGS.solver
84}
85
86pub fn catch_bugs() -> bool {
87 FLAGS.catch_bugs
88}
89
90pub fn annots() -> bool {
91 FLAGS.annots
92}
93
94pub fn timings() -> bool {
95 FLAGS.timings
96}
97
98pub fn verify() -> bool {
99 FLAGS.verify
100}
101
102pub fn full_compilation() -> bool {
103 FLAGS.full_compilation
104}
105
106#[derive(Clone, Debug, Deserialize)]
107#[serde(try_from = "String")]
108pub struct Pos {
109 pub file: String,
110 pub line: usize,
111 pub column: usize,
112}
113
114impl FromStr for Pos {
115 type Err = &'static str;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 let s = s.trim();
119 let parts: Vec<&str> = s.split(':').collect();
120 if parts.len() != 3 {
121 return Err("span format should be '<file>:<line>:<column>'");
122 }
123 let file = parts[0].to_string();
124 let line = parts[1]
125 .parse::<usize>()
126 .map_err(|_| "invalid line number")?;
127 let column = parts[2]
128 .parse::<usize>()
129 .map_err(|_| "invalid column number")?;
130 Ok(Pos { file, line, column })
131 }
132}
133
134impl TryFrom<String> for Pos {
135 type Error = &'static str;
136
137 fn try_from(value: String) -> Result<Self, Self::Error> {
138 value.parse()
139 }
140}
141
142impl fmt::Display for IncludePattern {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 write!(f, "[")?;
145 write!(f, "glob:{:?},", self.glob)?;
146 for def in &self.defs {
147 write!(f, "def:{},", def)?;
148 }
149 for pos in &self.spans {
150 write!(f, "span:{}:{}:{}", pos.file, pos.line, pos.column)?;
151 }
152 write!(f, "]")?;
153 Ok(())
154 }
155}
156#[derive(Clone)]
160pub struct IncludePattern {
161 pub glob: GlobSet,
163 pub defs: Vec<String>,
165 pub spans: Vec<Pos>,
167}
168
169impl IncludePattern {
170 fn new(includes: Vec<String>) -> Result<Self, String> {
171 let mut defs = Vec::new();
172 let mut spans = Vec::new();
173 let mut glob = GlobSetBuilder::new();
174 for include in includes {
175 if let Some(suffix) = include.strip_prefix("def:") {
176 defs.push(suffix.to_string());
177 } else if let Some(suffix) = include.strip_prefix("span:") {
178 spans.push(Pos::from_str(suffix)?);
179 } else {
180 let suffix = include.strip_prefix("glob:").unwrap_or(&include);
181 let glob_pattern = Glob::new(suffix.trim()).map_err(|_| "invalid glob pattern")?;
182 glob.add(glob_pattern);
183 }
184 }
185 let glob = glob.build().map_err(|_| "failed to build glob set")?;
186 Ok(IncludePattern { glob, defs, spans })
187 }
188}
189
190#[derive(Clone, Copy, Debug, Deserialize, Default)]
191#[serde(try_from = "String")]
192pub enum OverflowMode {
193 #[default]
195 None,
196 Lazy,
199 StrictUnder,
202 Strict,
205}
206
207impl OverflowMode {
208 const ERROR: &'static str = "expected one of `none`, `lazy`, or `strict`";
209}
210impl FromStr for OverflowMode {
211 type Err = &'static str;
212
213 fn from_str(s: &str) -> Result<Self, Self::Err> {
214 let s = s.to_ascii_lowercase();
215 match s.as_str() {
216 "none" => Ok(OverflowMode::None),
217 "lazy" => Ok(OverflowMode::Lazy),
218 "strict" => Ok(OverflowMode::Strict),
219 "strict-under" => Ok(OverflowMode::StrictUnder),
220 _ => Err(Self::ERROR),
221 }
222 }
223}
224
225impl TryFrom<String> for OverflowMode {
226 type Error = &'static str;
227
228 fn try_from(value: String) -> Result<Self, Self::Error> {
229 value.parse()
230 }
231}
232
233impl fmt::Display for OverflowMode {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 match self {
236 OverflowMode::None => write!(f, "none"),
237 OverflowMode::Lazy => write!(f, "lazy"),
238 OverflowMode::Strict => write!(f, "strict"),
239 OverflowMode::StrictUnder => write!(f, "strict-under"),
240 }
241 }
242}
243
244#[derive(Clone, Copy, Debug, Deserialize, Default)]
245#[serde(try_from = "String")]
246pub enum SmtSolver {
247 #[default]
248 Z3,
249 CVC5,
250}
251
252impl SmtSolver {
253 const ERROR: &'static str = "expected one of `z3` or `cvc5`";
254}
255
256impl FromStr for SmtSolver {
257 type Err = &'static str;
258
259 fn from_str(s: &str) -> Result<Self, Self::Err> {
260 let s = s.to_ascii_lowercase();
261 match s.as_str() {
262 "z3" => Ok(SmtSolver::Z3),
263 "cvc5" => Ok(SmtSolver::CVC5),
264 _ => Err(Self::ERROR),
265 }
266 }
267}
268
269impl TryFrom<String> for SmtSolver {
270 type Error = &'static str;
271
272 fn try_from(value: String) -> Result<Self, Self::Error> {
273 value.parse()
274 }
275}
276
277impl fmt::Display for SmtSolver {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 match self {
280 SmtSolver::Z3 => write!(f, "z3"),
281 SmtSolver::CVC5 => write!(f, "cvc5"),
282 }
283 }
284}
285
286#[derive(Clone, Copy, Debug)]
288pub struct InferOpts {
289 pub check_overflow: OverflowMode,
292 pub scrape_quals: bool,
294 pub solver: SmtSolver,
295 pub allow_uninterpreted_cast: bool,
297}
298
299impl From<PartialInferOpts> for InferOpts {
300 fn from(opts: PartialInferOpts) -> Self {
301 InferOpts {
302 check_overflow: opts.check_overflow.unwrap_or_else(check_overflow),
303 scrape_quals: opts.scrape_quals.unwrap_or_else(scrape_quals),
304 solver: opts.solver.unwrap_or_else(solver),
305 allow_uninterpreted_cast: opts
306 .allow_uninterpreted_cast
307 .unwrap_or_else(allow_uninterpreted_cast),
308 }
309 }
310}
311
312#[derive(Clone, Copy, Default, Deserialize, Debug)]
313pub struct PartialInferOpts {
314 pub check_overflow: Option<OverflowMode>,
315 pub scrape_quals: Option<bool>,
316 pub solver: Option<SmtSolver>,
317 pub allow_uninterpreted_cast: Option<bool>,
318}
319
320impl PartialInferOpts {
321 pub fn merge(&mut self, other: &Self) {
322 self.check_overflow = self.check_overflow.or(other.check_overflow);
323 self.allow_uninterpreted_cast = self
324 .allow_uninterpreted_cast
325 .or(other.allow_uninterpreted_cast);
326 self.scrape_quals = self.scrape_quals.or(other.scrape_quals);
327 self.solver = self.solver.or(other.solver);
328 }
329}
330
331#[derive(Copy, Clone, Deserialize, Default)]
332pub enum PointerWidth {
333 W32,
334 #[default]
335 W64,
336}
337
338impl PointerWidth {
339 const ERROR: &str = "pointer width must be 32 or 64";
340
341 pub fn bits(self) -> u64 {
342 match self {
343 PointerWidth::W32 => 32,
344 PointerWidth::W64 => 64,
345 }
346 }
347}
348
349impl FromStr for PointerWidth {
350 type Err = &'static str;
351
352 fn from_str(s: &str) -> Result<Self, Self::Err> {
353 match s {
354 "32" => Ok(PointerWidth::W32),
355 "64" => Ok(PointerWidth::W64),
356 _ => Err(Self::ERROR),
357 }
358 }
359}
360
361fn config_path() -> Option<PathBuf> {
362 let mut path = std::env::current_dir().unwrap();
364 loop {
365 for name in ["flux.toml", ".flux.toml"] {
366 let file = path.join(name);
367 if file.exists() {
368 return Some(file);
369 }
370 }
371 if !path.pop() {
372 return None;
373 }
374 }
375}
376
377pub static CONFIG_FILE: LazyLock<Value> = LazyLock::new(|| {
378 if let Some(path) = config_path() {
379 let mut file = std::fs::File::open(path).unwrap();
380 let mut contents = String::new();
381 file.read_to_string(&mut contents).unwrap();
382 toml::from_str(&contents).unwrap()
383 } else {
384 toml::from_str("").unwrap()
385 }
386});