1use crate::solve::{format_constraint, Counterexample, Verdict, VerificationReport};
8#[cfg(test)]
9use crate::sym::SymValue;
10use crate::sym::{Constraint, ConstraintSystem};
11
12pub struct JsonReport {
17 pub version: u32,
18 pub file: String,
19 pub verdict: String,
20 pub summary: JsonSummary,
21 pub constraints: Vec<JsonConstraint>,
22 pub counterexamples: Vec<JsonCounterexample>,
23 pub redundant_assertions: Vec<usize>,
24 pub suggestions: Vec<JsonSuggestion>,
25}
26
27pub struct JsonSummary {
28 pub total_constraints: usize,
29 pub active_constraints: usize,
30 pub variables: usize,
31 pub pub_inputs: usize,
32 pub divine_inputs: usize,
33 pub pub_outputs: usize,
34 pub static_violations: usize,
35 pub random_violations: usize,
36 pub bmc_violations: usize,
37}
38
39pub struct JsonConstraint {
40 pub index: usize,
41 pub kind: String,
42 pub expression: String,
43 pub is_trivial: bool,
44 pub is_violated: bool,
45}
46
47pub struct JsonCounterexample {
48 pub constraint_index: usize,
49 pub constraint_desc: String,
50 pub source: String,
51 pub assignments: Vec<(String, u64)>,
52}
53
54pub struct JsonSuggestion {
55 pub kind: String,
56 pub message: String,
57 pub constraint_index: Option<usize>,
58}
59
60fn json_escape(s: &str) -> String {
64 let mut out = String::with_capacity(s.len());
65 for ch in s.chars() {
66 match ch {
67 '"' => out.push_str("\\\""),
68 '\\' => out.push_str("\\\\"),
69 '\n' => out.push_str("\\n"),
70 '\r' => out.push_str("\\r"),
71 '\t' => out.push_str("\\t"),
72 c if (c as u32) < 0x20 => {
73 out.push_str(&format!("\\u{:04x}", c as u32));
74 }
75 c => out.push(c),
76 }
77 }
78 out
79}
80
81fn json_str(indent: usize, key: &str, value: &str) -> String {
83 let pad = " ".repeat(indent);
84 format!("{}\"{}\": \"{}\"", pad, key, json_escape(value))
85}
86
87fn json_uint(indent: usize, key: &str, value: usize) -> String {
89 let pad = " ".repeat(indent);
90 format!("{}\"{}\": {}", pad, key, value)
91}
92
93fn json_bool(indent: usize, key: &str, value: bool) -> String {
95 let pad = " ".repeat(indent);
96 format!(
97 "{}\"{}\": {}",
98 pad,
99 key,
100 if value { "true" } else { "false" }
101 )
102}
103
104pub fn format_json_constraint(c: &Constraint, index: usize) -> JsonConstraint {
108 let kind = match c {
109 Constraint::Equal(..) => "equal",
110 Constraint::AssertTrue(..) => "assert_true",
111 Constraint::Conditional(..) => "conditional",
112 Constraint::RangeU32(..) => "range_u32",
113 Constraint::DigestEqual(..) => "digest_equal",
114 };
115 JsonConstraint {
116 index,
117 kind: kind.to_string(),
118 expression: format_constraint(c),
119 is_trivial: c.is_trivial(),
120 is_violated: c.is_violated(),
121 }
122}
123
124fn convert_counterexample(ce: &Counterexample, source: &str) -> JsonCounterexample {
126 let mut assignments: Vec<(String, u64)> = ce
127 .assignments
128 .iter()
129 .filter(|(k, _)| !k.starts_with("__"))
130 .map(|(k, v)| (k.clone(), *v))
131 .collect();
132 assignments.sort_by(|(a, _), (b, _)| a.cmp(b));
133 JsonCounterexample {
134 constraint_index: ce.constraint_index,
135 constraint_desc: ce.constraint_desc.clone(),
136 source: source.to_string(),
137 assignments,
138 }
139}
140
141pub(super) mod suggestions;
144pub use suggestions::generate_suggestions;
145
146pub fn generate_json_report(
150 file_name: &str,
151 system: &ConstraintSystem,
152 report: &VerificationReport,
153) -> String {
154 let verdict_str = match report.verdict {
155 Verdict::Safe => "safe",
156 Verdict::StaticViolation | Verdict::RandomViolation | Verdict::BmcViolation => "unsafe",
157 };
158
159 let constraints: Vec<JsonConstraint> = system
160 .constraints
161 .iter()
162 .enumerate()
163 .map(|(i, c)| format_json_constraint(c, i))
164 .collect();
165
166 let mut counterexamples: Vec<JsonCounterexample> = Vec::new();
167 for ce in &report.random_result.counterexamples {
168 counterexamples.push(convert_counterexample(ce, "random"));
169 }
170 for ce in &report.bmc_result.counterexamples {
171 counterexamples.push(convert_counterexample(ce, "bmc"));
172 }
173
174 let suggestions = generate_suggestions(system, report);
175
176 let json_report = JsonReport {
177 version: 1,
178 file: file_name.to_string(),
179 verdict: verdict_str.to_string(),
180 summary: JsonSummary {
181 total_constraints: system.constraints.len(),
182 active_constraints: system.active_constraints(),
183 variables: system.num_variables as usize,
184 pub_inputs: system.pub_inputs.len(),
185 divine_inputs: system.divine_inputs.len(),
186 pub_outputs: system.pub_outputs.len(),
187 static_violations: report.static_violations.len(),
188 random_violations: report.random_result.counterexamples.len(),
189 bmc_violations: report.bmc_result.counterexamples.len(),
190 },
191 constraints,
192 counterexamples,
193 redundant_assertions: report.redundant_assertions.clone(),
194 suggestions,
195 };
196
197 serialize_report(&json_report)
198}
199
200fn serialize_report(r: &JsonReport) -> String {
203 let mut out = String::with_capacity(4096);
204 out.push_str("{\n");
205 out.push_str(&json_uint(2, "version", r.version as usize));
206 out.push_str(",\n");
207 out.push_str(&json_str(2, "file", &r.file));
208 out.push_str(",\n");
209 out.push_str(&json_str(2, "verdict", &r.verdict));
210 out.push_str(",\n");
211
212 out.push_str(" \"summary\": {\n");
214 out.push_str(&json_uint(
215 4,
216 "total_constraints",
217 r.summary.total_constraints,
218 ));
219 out.push_str(",\n");
220 out.push_str(&json_uint(
221 4,
222 "active_constraints",
223 r.summary.active_constraints,
224 ));
225 out.push_str(",\n");
226 out.push_str(&json_uint(4, "variables", r.summary.variables));
227 out.push_str(",\n");
228 out.push_str(&json_uint(4, "pub_inputs", r.summary.pub_inputs));
229 out.push_str(",\n");
230 out.push_str(&json_uint(4, "divine_inputs", r.summary.divine_inputs));
231 out.push_str(",\n");
232 out.push_str(&json_uint(4, "pub_outputs", r.summary.pub_outputs));
233 out.push_str(",\n");
234 out.push_str(&json_uint(
235 4,
236 "static_violations",
237 r.summary.static_violations,
238 ));
239 out.push_str(",\n");
240 out.push_str(&json_uint(
241 4,
242 "random_violations",
243 r.summary.random_violations,
244 ));
245 out.push_str(",\n");
246 out.push_str(&json_uint(4, "bmc_violations", r.summary.bmc_violations));
247 out.push('\n');
248 out.push_str(" },\n");
249
250 out.push_str(" \"constraints\": [\n");
252 for (i, c) in r.constraints.iter().enumerate() {
253 out.push_str(&serialize_constraint(c));
254 if i + 1 < r.constraints.len() {
255 out.push(',');
256 }
257 out.push('\n');
258 }
259 out.push_str(" ],\n");
260
261 out.push_str(" \"counterexamples\": [\n");
263 for (i, ce) in r.counterexamples.iter().enumerate() {
264 out.push_str(&serialize_counterexample(ce));
265 if i + 1 < r.counterexamples.len() {
266 out.push(',');
267 }
268 out.push('\n');
269 }
270 out.push_str(" ],\n");
271
272 out.push_str(" \"redundant_assertions\": [");
274 for (i, idx) in r.redundant_assertions.iter().enumerate() {
275 if i > 0 {
276 out.push_str(", ");
277 }
278 out.push_str(&idx.to_string());
279 }
280 out.push_str("],\n");
281
282 out.push_str(" \"suggestions\": [\n");
284 for (i, s) in r.suggestions.iter().enumerate() {
285 out.push_str(&serialize_suggestion(s));
286 if i + 1 < r.suggestions.len() {
287 out.push(',');
288 }
289 out.push('\n');
290 }
291 out.push_str(" ]\n");
292
293 out.push_str("}\n");
294 out
295}
296
297fn serialize_constraint(c: &JsonConstraint) -> String {
298 let mut out = String::new();
299 out.push_str(" {\n");
300 out.push_str(&json_uint(6, "index", c.index));
301 out.push_str(",\n");
302 out.push_str(&json_str(6, "kind", &c.kind));
303 out.push_str(",\n");
304 out.push_str(&json_str(6, "expression", &c.expression));
305 out.push_str(",\n");
306 out.push_str(&json_bool(6, "is_trivial", c.is_trivial));
307 out.push_str(",\n");
308 out.push_str(&json_bool(6, "is_violated", c.is_violated));
309 out.push('\n');
310 out.push_str(" }");
311 out
312}
313
314fn serialize_counterexample(ce: &JsonCounterexample) -> String {
315 let mut out = String::new();
316 out.push_str(" {\n");
317 out.push_str(&json_uint(6, "constraint_index", ce.constraint_index));
318 out.push_str(",\n");
319 out.push_str(&json_str(6, "constraint_desc", &ce.constraint_desc));
320 out.push_str(",\n");
321 out.push_str(&json_str(6, "source", &ce.source));
322 out.push_str(",\n");
323 out.push_str(" \"assignments\": {\n");
324 for (i, (name, value)) in ce.assignments.iter().enumerate() {
325 let pad = " ";
326 out.push_str(&format!("{}\"{}\": {}", pad, json_escape(name), value));
327 if i + 1 < ce.assignments.len() {
328 out.push(',');
329 }
330 out.push('\n');
331 }
332 out.push_str(" }\n");
333 out.push_str(" }");
334 out
335}
336
337fn serialize_suggestion(s: &JsonSuggestion) -> String {
338 let mut out = String::new();
339 out.push_str(" {\n");
340 out.push_str(&json_str(6, "kind", &s.kind));
341 out.push_str(",\n");
342 out.push_str(&json_str(6, "message", &s.message));
343 out.push_str(",\n");
344 let pad = " ";
345 match s.constraint_index {
346 Some(idx) => out.push_str(&format!("{}\"constraint_index\": {}\n", pad, idx)),
347 None => out.push_str(&format!("{}\"constraint_index\": null\n", pad)),
348 }
349 out.push_str(" }");
350 out
351}
352
353#[cfg(test)]
356mod tests;