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