1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
use binaryninja::interaction::{Form, FormInputField};
use minijinja::Environment;
use serde::Serialize;
use warp::chunk::{Chunk, ChunkKind};
use warp::r#type::guid::TypeGUID;
use warp::WarpFile;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum ReportKindField {
None,
#[default]
Html,
Markdown,
Json,
}
impl ReportKindField {
pub fn to_field(&self) -> FormInputField {
FormInputField::Choice {
prompt: "Generated Report".to_string(),
choices: vec![
"None".to_string(),
"HTML".to_string(),
"Markdown".to_string(),
"JSON".to_string(),
],
default: Some(match self {
Self::None => 0,
Self::Html => 1,
Self::Markdown => 2,
Self::Json => 3,
}),
value: 0,
}
}
pub fn from_form(form: &Form) -> Option<Self> {
let field = form.get_field_with_name("Generated Report")?;
let field_value = field.try_value_index()?;
match field_value {
3 => Some(Self::Json),
2 => Some(Self::Markdown),
1 => Some(Self::Html),
_ => Some(Self::None),
}
}
}
#[derive(Debug, Clone)]
pub struct ReportGenerator {
environment: Environment<'static>,
}
impl ReportGenerator {
pub fn new() -> Self {
let mut environment = Environment::new();
// Remove trailing lines for blocks, this is required for Markdown tables.
environment.set_trim_blocks(true);
minijinja_embed::load_templates!(&mut environment);
Self { environment }
}
pub fn report(&self, kind: &ReportKindField, file: &WarpFile) -> Option<String> {
match kind {
ReportKindField::None => None,
ReportKindField::Html => self.html_report(file),
ReportKindField::Markdown => self.markdown_report(file),
ReportKindField::Json => self.json_report(file),
}
}
pub fn report_extension(&self, kind: &ReportKindField) -> Option<&'static str> {
match kind {
ReportKindField::None => None,
ReportKindField::Html => Some("html"),
ReportKindField::Markdown => Some("md"),
ReportKindField::Json => Some("json"),
}
}
pub fn html_report(&self, file: &WarpFile) -> Option<String> {
let data = FileReportData::new(file);
let tmpl = self.environment.get_template("file.html").ok()?;
tmpl.render(data).ok()
}
pub fn markdown_report(&self, file: &WarpFile) -> Option<String> {
let data = FileReportData::new(file);
let tmpl = self.environment.get_template("file.md").ok()?;
tmpl.render(data).ok()
}
pub fn json_report(&self, file: &WarpFile) -> Option<String> {
let data = FileReportData::new(file);
let tmpl = self.environment.get_template("file.json").ok()?;
tmpl.render(data).ok()
}
}
#[derive(Debug, Clone, Serialize)]
pub struct FileReportData {
pub title: String,
// pub header: WarpFileHeader,
pub chunks: Vec<ChunkReportData>,
}
impl FileReportData {
pub fn new(file: &WarpFile) -> Self {
Self {
title: "Warp File Report".to_string(),
// header: file.header.clone(),
chunks: file
.chunks
.iter()
.map(|chunk| ChunkReportData::new(chunk))
.collect(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ChunkReportData {
pub title: String,
// pub header: ChunkHeader,
pub target: String,
pub total_item_count: usize,
/// View into a (possible subset) of chunk items.
pub item_view: Vec<ItemReportData>,
}
impl ChunkReportData {
pub fn new(chunk: &Chunk) -> Self {
// TODO: Set a limit for the number of items so we dont construct 10000000 items in the report.
let items: Vec<_> = match &chunk.kind {
ChunkKind::Signature(sc) => sc
.raw_functions()
.map(|f| ItemReportData {
name: f.symbol().and_then(|s| s.name().map(|n| n.to_string())),
guid: f.guid().to_string(),
note: None,
})
.collect(),
ChunkKind::Type(tc) => tc
.raw_types()
.map(|t| ItemReportData {
name: t.type_().and_then(|s| s.name().map(|n| n.to_string())),
guid: TypeGUID::from(t.guid()).to_string(),
note: None,
})
.collect(),
};
let chunk_type = match &chunk.kind {
ChunkKind::Signature(_) => "Signature".to_string(),
ChunkKind::Type(_) => "Type".to_string(),
};
let size_in_kb = chunk.header.size as f64 / 1024.0;
let formatted_size = format!("{:.1}kb", size_in_kb);
// For the target show the platform, or the architecture if available.
let target = chunk
.header
.target
.platform
.clone()
.or_else(|| chunk.header.target.architecture.clone())
.unwrap_or_else(|| "None".to_string());
Self {
title: format!("{} Chunk ({})", chunk_type, formatted_size),
target,
// header: chunk.header.clone(),
total_item_count: items.len(),
item_view: items,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ItemReportData {
pub guid: String,
pub name: Option<String>,
pub note: Option<String>,
}
|