Skip to content

Commit 1953799

Browse files
committed
Support for progress categories & linked stats
1 parent 3bd8aae commit 1953799

File tree

10 files changed

+218
-31
lines changed

10 files changed

+218
-31
lines changed

objdiff-cli/src/cmd/report.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use argp::FromArgs;
1111
use objdiff_core::{
1212
bindings::report::{
1313
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
14-
ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
14+
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
15+
REPORT_VERSION,
1516
},
1617
config::ProjectObject,
1718
diff, obj,
@@ -129,7 +130,17 @@ fn generate(args: GenerateArgs) -> Result<()> {
129130
units = vec.into_iter().flatten().collect();
130131
}
131132
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
132-
let report = Report { measures: Some(measures), units };
133+
let mut categories = Vec::new();
134+
for category in &project.progress_categories {
135+
categories.push(ReportCategory {
136+
id: category.id.clone(),
137+
name: category.name.clone(),
138+
measures: Some(Default::default()),
139+
});
140+
}
141+
let mut report =
142+
Report { measures: Some(measures), units, version: REPORT_VERSION, categories };
143+
report.calculate_progress_categories();
133144
let duration = start.elapsed();
134145
info!("Report generated in {}.{:03}s", duration.as_secs(), duration.subsec_millis());
135146
write_output(&report, args.output.as_deref(), output_format)?;
@@ -145,7 +156,7 @@ fn report_object(
145156
) -> Result<Option<ReportUnit>> {
146157
object.resolve_paths(project_dir, target_dir, base_dir);
147158
match (&object.target_path, &object.base_path) {
148-
(None, Some(_)) if object.complete != Some(true) => {
159+
(None, Some(_)) if !object.complete().unwrap_or(false) => {
149160
warn!("Skipping object without target: {}", object.name());
150161
return Ok(None);
151162
}
@@ -173,13 +184,19 @@ fn report_object(
173184
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
174185

175186
let metadata = ReportUnitMetadata {
176-
complete: object.complete,
187+
complete: object.complete(),
177188
module_name: target
178189
.as_ref()
179190
.and_then(|o| o.split_meta.as_ref())
180191
.and_then(|m| m.module_name.clone()),
181192
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
182-
source_path: None, // TODO
193+
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
194+
progress_categories: object
195+
.metadata
196+
.as_ref()
197+
.and_then(|m| m.progress_categories.clone())
198+
.unwrap_or_default(),
199+
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
183200
};
184201
let mut measures = Measures::default();
185202
let mut sections = vec![];
@@ -191,7 +208,7 @@ fn report_object(
191208
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
192209
// Support cases where we don't have a target object,
193210
// assume complete means 100% match
194-
if object.complete == Some(true) {
211+
if object.complete().unwrap_or(false) {
195212
100.0
196213
} else {
197214
0.0
@@ -233,7 +250,7 @@ fn report_object(
233250
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
234251
// Support cases where we don't have a target object,
235252
// assume complete means 100% match
236-
if object.complete == Some(true) {
253+
if object.complete().unwrap_or(false) {
237254
100.0
238255
} else {
239256
0.0
@@ -259,6 +276,10 @@ fn report_object(
259276
measures.total_functions += 1;
260277
}
261278
}
279+
if metadata.complete.unwrap_or(false) {
280+
measures.complete_code = measures.total_code;
281+
measures.complete_data = measures.total_data;
282+
}
262283
measures.calc_fuzzy_match_percent();
263284
measures.calc_matched_percent();
264285
Ok(Some(ReportUnit {

objdiff-core/Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@ A local diffing tool for decompilation projects.
1515
crate-type = ["cdylib", "rlib"]
1616

1717
[features]
18-
all = ["config", "dwarf", "mips", "ppc", "x86", "arm"]
18+
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
1919
any-arch = [] # Implicit, used to check if any arch is enabled
2020
config = ["globset", "semver", "serde_json", "serde_yaml"]
2121
dwarf = ["gimli"]
2222
mips = ["any-arch", "rabbitizer"]
2323
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
2424
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
2525
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
26-
wasm = ["serde_json", "console_error_panic_hook", "console_log"]
26+
bindings = ["serde_json", "prost", "pbjson"]
27+
wasm = ["bindings", "console_error_panic_hook", "console_log"]
2728

2829
[dependencies]
2930
anyhow = "1.0.82"
@@ -34,8 +35,8 @@ log = "0.4.21"
3435
memmap2 = "0.9.4"
3536
num-traits = "0.2.18"
3637
object = { version = "0.35.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
37-
pbjson = "0.7.0"
38-
prost = "0.13.1"
38+
pbjson = { version = "0.7.0", optional = true }
39+
prost = { version = "0.13.1", optional = true }
3940
serde = { version = "1", features = ["derive"] }
4041
similar = { version = "2.5.0", default-features = false }
4142
strum = { version = "0.26.2", features = ["derive"] }
1.58 KB
Binary file not shown.

objdiff-core/protos/report.proto

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ message Measures {
2424
uint32 matched_functions = 9;
2525
// Fully matched functions percent
2626
float matched_functions_percent = 10;
27+
// Completed (or "linked") code size in bytes
28+
uint64 complete_code = 11;
29+
// Completed (or "linked") code percent
30+
float complete_code_percent = 12;
31+
// Completed (or "linked") data size in bytes
32+
uint64 complete_data = 13;
33+
// Completed (or "linked") data percent
34+
float complete_data_percent = 14;
2735
}
2836

2937
// Project progress report
@@ -32,6 +40,19 @@ message Report {
3240
Measures measures = 1;
3341
// Units within this report
3442
repeated ReportUnit units = 2;
43+
// Report version
44+
uint32 version = 3;
45+
// Progress categories
46+
repeated ReportCategory categories = 4;
47+
}
48+
49+
message ReportCategory {
50+
// The ID of the category
51+
string id = 1;
52+
// The name of the category
53+
string name = 2;
54+
// Progress info for this category
55+
Measures measures = 3;
3556
}
3657

3758
// A unit of the report (usually a translation unit)
@@ -58,6 +79,10 @@ message ReportUnitMetadata {
5879
optional uint32 module_id = 3;
5980
// The path to the source file of this unit
6081
optional string source_path = 4;
82+
// Progress categories for this unit
83+
repeated string progress_categories = 5;
84+
// Whether this unit is automatically generated (not user-provided)
85+
optional bool auto_generated = 6;
6186
}
6287

6388
// A section or function within a unit

objdiff-core/src/bindings/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#[cfg(feature = "any-arch")]
12
pub mod diff;
23
pub mod report;
34
#[cfg(feature = "wasm")]

objdiff-core/src/bindings/report.rs

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ops::AddAssign;
2+
13
use anyhow::{bail, Result};
24
use prost::Message;
35
use serde_json::error::Category;
@@ -6,18 +8,21 @@ use serde_json::error::Category;
68
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
79
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
810

11+
pub const REPORT_VERSION: u32 = 1;
12+
913
impl Report {
1014
pub fn parse(data: &[u8]) -> Result<Self> {
1115
if data.is_empty() {
1216
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
1317
}
14-
if data[0] == b'{' {
18+
let report = if data[0] == b'{' {
1519
// Load as JSON
16-
Self::from_json(data).map_err(anyhow::Error::new)
20+
Self::from_json(data)?
1721
} else {
1822
// Load as binary protobuf
19-
Self::decode(data).map_err(anyhow::Error::new)
20-
}
23+
Self::decode(data)?
24+
};
25+
Ok(report)
2126
}
2227

2328
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
@@ -37,6 +42,81 @@ impl Report {
3742
}
3843
}
3944
}
45+
46+
pub fn migrate(&mut self) -> Result<()> {
47+
if self.version == 0 {
48+
self.migrate_v0()?;
49+
}
50+
if self.version != REPORT_VERSION {
51+
bail!("Unsupported report version: {}", self.version);
52+
}
53+
Ok(())
54+
}
55+
56+
fn migrate_v0(&mut self) -> Result<()> {
57+
let Some(measures) = &mut self.measures else {
58+
bail!("Missing measures in report");
59+
};
60+
for unit in &mut self.units {
61+
let Some(unit_measures) = &mut unit.measures else {
62+
bail!("Missing measures in report unit");
63+
};
64+
let Some(metadata) = &mut unit.metadata else {
65+
bail!("Missing metadata in report unit");
66+
};
67+
if metadata.module_name.is_some() || metadata.module_id.is_some() {
68+
metadata.progress_categories = vec!["modules".to_string()];
69+
} else {
70+
metadata.progress_categories = vec!["dol".to_string()];
71+
}
72+
if metadata.complete.unwrap_or(false) {
73+
unit_measures.complete_code = unit_measures.total_code;
74+
unit_measures.complete_data = unit_measures.total_data;
75+
unit_measures.complete_code_percent = 100.0;
76+
unit_measures.complete_data_percent = 100.0;
77+
} else {
78+
unit_measures.complete_code = 0;
79+
unit_measures.complete_data = 0;
80+
unit_measures.complete_code_percent = 0.0;
81+
unit_measures.complete_data_percent = 0.0;
82+
}
83+
measures.complete_code += unit_measures.complete_code;
84+
measures.complete_data += unit_measures.complete_data;
85+
}
86+
measures.calc_matched_percent();
87+
self.version = 1;
88+
Ok(())
89+
}
90+
91+
pub fn calculate_progress_categories(&mut self) {
92+
for unit in &self.units {
93+
let Some(metadata) = unit.metadata.as_ref() else {
94+
continue;
95+
};
96+
let Some(measures) = unit.measures.as_ref() else {
97+
continue;
98+
};
99+
for category_id in &metadata.progress_categories {
100+
let category = match self.categories.iter_mut().find(|c| &c.id == category_id) {
101+
Some(category) => category,
102+
None => {
103+
self.categories.push(ReportCategory {
104+
id: category_id.clone(),
105+
name: String::new(),
106+
measures: Some(Default::default()),
107+
});
108+
self.categories.last_mut().unwrap()
109+
}
110+
};
111+
*category.measures.get_or_insert_with(Default::default) += *measures;
112+
}
113+
}
114+
for category in &mut self.categories {
115+
let measures = category.measures.get_or_insert_with(Default::default);
116+
measures.calc_fuzzy_match_percent();
117+
measures.calc_matched_percent();
118+
}
119+
}
40120
}
41121

42122
impl Measures {
@@ -66,6 +146,16 @@ impl Measures {
66146
} else {
67147
self.matched_functions as f32 / self.total_functions as f32 * 100.0
68148
};
149+
self.complete_code_percent = if self.total_code == 0 {
150+
100.0
151+
} else {
152+
self.complete_code as f32 / self.total_code as f32 * 100.0
153+
};
154+
self.complete_data_percent = if self.total_data == 0 {
155+
100.0
156+
} else {
157+
self.complete_data as f32 / self.total_data as f32 * 100.0
158+
};
69159
}
70160
}
71161

@@ -75,19 +165,27 @@ impl From<&ReportItem> for ChangeItemInfo {
75165
}
76166
}
77167

168+
impl AddAssign for Measures {
169+
fn add_assign(&mut self, other: Self) {
170+
self.fuzzy_match_percent += other.fuzzy_match_percent * other.total_code as f32;
171+
self.total_code += other.total_code;
172+
self.matched_code += other.matched_code;
173+
self.total_data += other.total_data;
174+
self.matched_data += other.matched_data;
175+
self.total_functions += other.total_functions;
176+
self.matched_functions += other.matched_functions;
177+
self.complete_code += other.complete_code;
178+
self.complete_data += other.complete_data;
179+
}
180+
}
181+
78182
/// Allows [collect](Iterator::collect) to be used on an iterator of [Measures].
79183
impl FromIterator<Measures> for Measures {
80184
fn from_iter<T>(iter: T) -> Self
81185
where T: IntoIterator<Item = Measures> {
82186
let mut measures = Measures::default();
83187
for other in iter {
84-
measures.fuzzy_match_percent += other.fuzzy_match_percent * other.total_code as f32;
85-
measures.total_code += other.total_code;
86-
measures.matched_code += other.matched_code;
87-
measures.total_data += other.total_data;
88-
measures.matched_data += other.matched_data;
89-
measures.total_functions += other.total_functions;
90-
measures.matched_functions += other.matched_functions;
188+
measures += other;
91189
}
92190
measures.calc_fuzzy_match_percent();
93191
measures.calc_matched_percent();
@@ -125,8 +223,10 @@ impl From<LegacyReport> for Report {
125223
total_functions: value.total_functions,
126224
matched_functions: value.matched_functions,
127225
matched_functions_percent: value.matched_functions_percent,
226+
..Default::default()
128227
}),
129-
units: value.units.into_iter().map(ReportUnit::from).collect(),
228+
units: value.units.into_iter().map(ReportUnit::from).collect::<Vec<_>>(),
229+
..Default::default()
130230
}
131231
}
132232
}

0 commit comments

Comments
 (0)