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