panic_attacker/report/
formatter.rs1use crate::types::*;
6use anyhow::Result;
7use colored::*;
8use std::fs;
9use std::path::Path;
10
11pub struct ReportFormatter;
12
13impl ReportFormatter {
14 pub fn new() -> Self {
15 Self
16 }
17
18 pub fn print(&self, report: &AssaultReport) {
19 println!(
20 "\n{}",
21 "=== PANIC-ATTACKER ASSAULT REPORT ===".bold().cyan()
22 );
23 println!();
24
25 self.print_xray_summary(&report.xray_report);
26 println!();
27
28 self.print_attack_summary(&report.attack_results);
29 println!();
30
31 self.print_signatures(&report.attack_results);
32 println!();
33
34 self.print_overall_assessment(&report.overall_assessment);
35 println!();
36 }
37
38 fn print_xray_summary(&self, xray: &XRayReport) {
39 println!("{}", "X-RAY ANALYSIS".bold().yellow());
40 println!(" Program: {}", xray.program_path.display());
41 println!(" Language: {:?}", xray.language);
42 println!(" Frameworks: {:?}", xray.frameworks);
43 println!();
44
45 println!(" Statistics:");
46 println!(" Total lines: {}", xray.statistics.total_lines);
47 println!(" Unsafe blocks: {}", xray.statistics.unsafe_blocks);
48 println!(" Panic sites: {}", xray.statistics.panic_sites);
49 println!(" Unwrap calls: {}", xray.statistics.unwrap_calls);
50 println!(" Allocation sites: {}", xray.statistics.allocation_sites);
51 println!(" I/O operations: {}", xray.statistics.io_operations);
52 println!(
53 " Threading constructs: {}",
54 xray.statistics.threading_constructs
55 );
56 println!();
57
58 if !xray.weak_points.is_empty() {
59 println!(" Weak Points Detected: {}", xray.weak_points.len());
60 for (i, wp) in xray.weak_points.iter().enumerate() {
61 let severity_color = match wp.severity {
62 Severity::Critical => "red",
63 Severity::High => "yellow",
64 Severity::Medium => "blue",
65 Severity::Low => "green",
66 };
67 println!(
68 " {}. [{:?}] {} - {}",
69 i + 1,
70 wp.severity.to_string().color(severity_color),
71 format!("{:?}", wp.category).bold(),
72 wp.description
73 );
74 }
75 }
76
77 if !xray.file_statistics.is_empty() {
79 println!();
80 println!(" Per-file Breakdown (top 10 by risk):");
81
82 let mut scored: Vec<_> = xray
83 .file_statistics
84 .iter()
85 .map(|fs| {
86 let risk = fs.unsafe_blocks * 3
87 + fs.panic_sites * 2
88 + fs.unwrap_calls
89 + fs.threading_constructs * 2;
90 (risk, fs)
91 })
92 .collect();
93 scored.sort_by(|a, b| b.0.cmp(&a.0));
94
95 for (rank, (risk, fs)) in scored.iter().take(10).enumerate() {
96 println!(
97 " {}. {} (risk: {}, unsafe: {}, panics: {}, unwraps: {}, threads: {})",
98 rank + 1,
99 fs.file_path.bold(),
100 risk,
101 fs.unsafe_blocks,
102 fs.panic_sites,
103 fs.unwrap_calls,
104 fs.threading_constructs,
105 );
106 }
107
108 if scored.len() > 10 {
109 println!(" ... and {} more files", scored.len() - 10);
110 }
111 }
112 }
113
114 fn print_attack_summary(&self, results: &[AttackResult]) {
115 println!("{}", "ATTACK RESULTS".bold().yellow());
116
117 for result in results {
118 let status = if result.success {
119 "PASSED".green()
120 } else {
121 "FAILED".red()
122 };
123
124 println!(
125 " {:?} attack: {} (exit code: {:?}, duration: {:.2}s)",
126 result.axis,
127 status,
128 result.exit_code,
129 result.duration.as_secs_f64()
130 );
131
132 if !result.crashes.is_empty() {
133 println!(
134 " Crashes: {}",
135 result.crashes.len().to_string().red().bold()
136 );
137 for (i, crash) in result.crashes.iter().enumerate() {
138 println!(" {}. Signal: {:?}", i + 1, crash.signal);
139 if let Some(bt) = &crash.backtrace {
140 println!(" Backtrace available: {} bytes", bt.len());
141 }
142 }
143 }
144
145 if result.peak_memory > 0 {
146 println!(" Peak memory: {} MB", result.peak_memory / (1024 * 1024));
147 }
148 }
149 }
150
151 fn print_signatures(&self, results: &[AttackResult]) {
152 let total_sigs: usize = results.iter().map(|r| r.signatures_detected.len()).sum();
153
154 if total_sigs > 0 {
155 println!("{}", "BUG SIGNATURES DETECTED".bold().red());
156 println!(" Total: {}", total_sigs);
157 println!();
158
159 for result in results {
160 if !result.signatures_detected.is_empty() {
161 println!(" During {:?} attack:", result.axis);
162 for sig in &result.signatures_detected {
163 println!(
164 " - {:?} (confidence: {:.2})",
165 sig.signature_type, sig.confidence
166 );
167 for evidence in &sig.evidence {
168 println!(" Evidence: {}", evidence.dimmed());
169 }
170 if let Some(loc) = &sig.location {
171 println!(" Location: {}", loc.dimmed());
172 }
173 }
174 println!();
175 }
176 }
177 } else {
178 println!("{}", "No bug signatures detected".green());
179 }
180 }
181
182 fn print_overall_assessment(&self, assessment: &OverallAssessment) {
183 println!("{}", "OVERALL ASSESSMENT".bold().yellow());
184
185 let score_color = if assessment.robustness_score >= 80.0 {
186 "green"
187 } else if assessment.robustness_score >= 50.0 {
188 "yellow"
189 } else {
190 "red"
191 };
192
193 println!(
194 " Robustness Score: {}/100",
195 format!("{:.1}", assessment.robustness_score)
196 .color(score_color)
197 .bold()
198 );
199 println!();
200
201 if !assessment.critical_issues.is_empty() {
202 println!(" Critical Issues:");
203 for issue in &assessment.critical_issues {
204 println!(" - {}", issue.red());
205 }
206 println!();
207 }
208
209 if !assessment.recommendations.is_empty() {
210 println!(" Recommendations:");
211 for rec in &assessment.recommendations {
212 println!(" - {}", rec);
213 }
214 }
215 }
216
217 pub fn save<P: AsRef<Path>>(&self, report: &AssaultReport, path: P) -> Result<()> {
218 let json = serde_json::to_string_pretty(report)?;
219 fs::write(path.as_ref(), json)?;
220 println!("Report saved to: {}", path.as_ref().display());
221 Ok(())
222 }
223}
224
225impl Default for ReportFormatter {
226 fn default() -> Self {
227 Self::new()
228 }
229}