1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum Frontend {
12 #[default]
14 Llvm,
15 #[serde(rename = "air-json")]
17 AirJson,
18 #[serde(untagged)]
21 Other(String),
22}
23
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum AnalysisMode {
28 Fast,
30 #[default]
32 Precise,
33}
34
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum ConfigFieldSensitivity {
43 None,
45 #[default]
47 StructFields,
48}
49
50#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum ExternalSideEffects {
54 None,
56 UnknownWrite,
58 #[default]
60 UnknownReadwrite,
61}
62
63#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
65pub struct Config {
66 #[serde(default)]
68 pub frontend: Frontend,
69
70 #[serde(default)]
72 pub analysis: AnalysisConfig,
73
74 #[serde(default)]
76 pub external_side_effects: ExternalSideEffects,
77
78 #[serde(default)]
80 pub paths: PathsConfig,
81
82 #[serde(default)]
84 pub rust: RustConfig,
85
86 #[serde(default)]
88 pub incremental: IncrementalConfig,
89}
90
91#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
93pub struct AnalysisConfig {
94 #[serde(default)]
96 pub mode: AnalysisMode,
97}
98
99#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
101pub struct PathsConfig {
102 #[serde(default)]
104 pub strip_prefixes: Vec<String>,
105
106 #[serde(default)]
108 pub normalize_separators: bool,
109}
110
111#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
113pub struct RustConfig {
114 #[serde(default)]
116 pub demangle: bool,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
121pub enum PtaSolver {
122 #[default]
124 Worklist,
125 Datalog,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134pub struct IncrementalConfig {
135 pub enabled: bool,
137
138 pub cache_dir: std::path::PathBuf,
140
141 pub split_strategy: crate::program::SplitStrategy,
143}
144
145impl Default for IncrementalConfig {
146 fn default() -> Self {
147 Self {
148 enabled: false,
149 cache_dir: std::path::PathBuf::from(".saf-cache"),
150 split_strategy: crate::program::SplitStrategy::Auto,
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn default_config_roundtrips() {
161 let config = Config::default();
162 let json = serde_json::to_string(&config).unwrap();
163 let back: Config = serde_json::from_str(&json).unwrap();
164 assert_eq!(config, back);
165 }
166
167 #[test]
168 fn default_values_match_srs() {
169 let config = Config::default();
170 assert_eq!(config.frontend, Frontend::Llvm);
171 assert_eq!(config.analysis.mode, AnalysisMode::Precise);
172 assert_eq!(
173 config.external_side_effects,
174 ExternalSideEffects::UnknownReadwrite
175 );
176 }
177
178 #[test]
179 fn serde_json_backward_compatibility() {
180 let config = Config::default();
182 let json = serde_json::to_value(&config).unwrap();
183 assert_eq!(json["frontend"], "llvm");
184 assert_eq!(json["analysis"]["mode"], "precise");
185 assert_eq!(json["external_side_effects"], "unknown_readwrite");
186
187 let air_json = serde_json::to_value(Frontend::AirJson).unwrap();
189 assert_eq!(air_json, "air-json");
190
191 let old_json = r#"{
193 "frontend": "air-json",
194 "analysis": { "mode": "fast" },
195 "external_side_effects": "unknown_write"
196 }"#;
197 let parsed: Config = serde_json::from_str(old_json).unwrap();
198 assert_eq!(parsed.frontend, Frontend::AirJson);
199 assert_eq!(parsed.analysis.mode, AnalysisMode::Fast);
200 assert_eq!(
201 parsed.external_side_effects,
202 ExternalSideEffects::UnknownWrite
203 );
204 }
205
206 #[test]
207 fn custom_frontend_roundtrips() {
208 let frontend = Frontend::Other("my-custom-frontend".to_string());
209 let json = serde_json::to_string(&frontend).unwrap();
210 assert_eq!(json, "\"my-custom-frontend\"");
212
213 let parsed: Frontend = serde_json::from_str(&json).unwrap();
214 assert_eq!(parsed, Frontend::Other("my-custom-frontend".to_string()));
215 }
216
217 #[test]
218 fn known_frontends_still_deserialize_correctly() {
219 let parsed: Frontend = serde_json::from_str("\"llvm\"").unwrap();
221 assert_eq!(parsed, Frontend::Llvm);
222
223 let parsed: Frontend = serde_json::from_str("\"air-json\"").unwrap();
224 assert_eq!(parsed, Frontend::AirJson);
225 }
226}