Refactor data diffing & expose WASM API (#256)

* Refactor data diffing & expose WASM API

* Update test snapshots
This commit is contained in:
2025-09-07 18:59:46 -06:00
committed by GitHub
parent f7cb494a62
commit fbdaa89cc0
14 changed files with 444 additions and 255 deletions

View File

@@ -1,11 +1,11 @@
use std::{cmp::min, default::Default, mem::take};
use std::default::Default;
use egui::{Label, Sense, Widget, text::LayoutJob};
use objdiff_core::{
diff::{
DataDiff, DataDiffKind, DataRelocationDiff,
data::resolve_relocation,
display::{ContextItem, HoverItem, HoverItemColor, relocation_context, relocation_hover},
DataDiffKind, DataDiffRow,
data::BYTES_PER_ROW,
display::{data_row_context, data_row_hover},
},
obj::Object,
};
@@ -13,84 +13,30 @@ use objdiff_core::{
use super::diff::{context_menu_items_ui, hover_items_ui};
use crate::views::{appearance::Appearance, write_text};
pub(crate) const BYTES_PER_ROW: usize = 16;
fn data_row_hover(obj: &Object, diffs: &[(DataDiff, Vec<DataRelocationDiff>)]) -> Vec<HoverItem> {
let mut out = Vec::new();
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
let mut prev_reloc = None;
let mut first = true;
for reloc_diff in reloc_diffs {
let reloc = &reloc_diff.reloc;
if prev_reloc == Some(reloc) {
// Avoid showing consecutive duplicate relocations.
// We do this because a single relocation can span across multiple diffs if the
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
continue;
}
prev_reloc = Some(reloc);
if first {
first = false;
} else {
out.push(HoverItem::Separator);
}
let color = get_hover_item_color_for_diff_kind(reloc_diff.kind);
let reloc = resolve_relocation(&obj.symbols, reloc);
out.append(&mut relocation_hover(obj, reloc, Some(color)));
}
out
}
fn data_row_context(
obj: &Object,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
) -> Vec<ContextItem> {
let mut out = Vec::new();
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
let mut prev_reloc = None;
for reloc_diff in reloc_diffs {
let reloc = &reloc_diff.reloc;
if prev_reloc == Some(reloc) {
// Avoid showing consecutive duplicate relocations.
// We do this because a single relocation can span across multiple diffs if the
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
continue;
}
prev_reloc = Some(reloc);
let reloc = resolve_relocation(&obj.symbols, reloc);
out.append(&mut relocation_context(obj, reloc, None));
}
out
}
fn data_row_hover_ui(
ui: &mut egui::Ui,
obj: &Object,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
diff_row: &DataDiffRow,
appearance: &Appearance,
) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
hover_items_ui(ui, data_row_hover(obj, diffs), appearance);
hover_items_ui(ui, data_row_hover(obj, diff_row), appearance);
});
}
fn data_row_context_menu(
ui: &mut egui::Ui,
obj: &Object,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
diff_row: &DataDiffRow,
column: usize,
appearance: &Appearance,
) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
context_menu_items_ui(ui, data_row_context(obj, diffs), column, appearance);
context_menu_items_ui(ui, data_row_context(obj, diff_row), column, appearance);
});
}
@@ -103,27 +49,18 @@ fn get_color_for_diff_kind(diff_kind: DataDiffKind, appearance: &Appearance) ->
}
}
fn get_hover_item_color_for_diff_kind(diff_kind: DataDiffKind) -> HoverItemColor {
match diff_kind {
DataDiffKind::None => HoverItemColor::Normal,
DataDiffKind::Replace => HoverItemColor::Special,
DataDiffKind::Delete => HoverItemColor::Delete,
DataDiffKind::Insert => HoverItemColor::Insert,
}
}
pub(crate) fn data_row_ui(
ui: &mut egui::Ui,
obj: Option<&Object>,
base_address: usize,
row_address: usize,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
base_address: u64,
row_address: u64,
diff_row: &DataDiffRow,
appearance: &Appearance,
column: usize,
) {
if diffs.iter().any(|(dd, rds)| {
dd.kind != DataDiffKind::None || rds.iter().any(|rd| rd.kind != DataDiffKind::None)
}) {
if diff_row.segments.iter().any(|dd| dd.kind != DataDiffKind::None)
|| diff_row.relocations.iter().any(|rd| rd.kind != DataDiffKind::None)
{
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
}
let mut job = LayoutJob::default();
@@ -137,20 +74,21 @@ pub(crate) fn data_row_ui(
let mut cur_addr = 0usize;
// The offset into the actual bytes of the section on this side, ignoring differences.
let mut cur_addr_actual = base_address + row_address;
for (diff, reloc_diffs) in diffs {
for diff in diff_row.segments.iter() {
let base_color = get_color_for_diff_kind(diff.kind, appearance);
if diff.data.is_empty() {
let mut str = " ".repeat(diff.len);
let mut str = " ".repeat(diff.size);
let n1 = cur_addr / 8;
let n2 = (diff.len + cur_addr) / 8;
let n2 = (diff.size + cur_addr) / 8;
str.push_str(" ".repeat(n2 - n1).as_str());
write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone());
cur_addr += diff.len;
cur_addr += diff.size;
} else {
for byte in &diff.data {
let mut byte_text = format!("{byte:02x} ");
let mut byte_color = base_color;
if let Some(reloc_diff) = reloc_diffs
if let Some(reloc_diff) = diff_row
.relocations
.iter()
.find(|reloc_diff| reloc_diff.range.contains(&cur_addr_actual))
{
@@ -179,11 +117,11 @@ pub(crate) fn data_row_ui(
write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone());
}
write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone());
for (diff, _) in diffs {
for diff in diff_row.segments.iter() {
let base_color = get_color_for_diff_kind(diff.kind, appearance);
if diff.data.is_empty() {
write_text(
" ".repeat(diff.len).as_str(),
" ".repeat(diff.size).as_str(),
base_color,
&mut job,
appearance.code_font.clone(),
@@ -204,70 +142,7 @@ pub(crate) fn data_row_ui(
let response = Label::new(job).sense(Sense::click()).ui(ui);
if let Some(obj) = obj {
response.context_menu(|ui| data_row_context_menu(ui, obj, diffs, column, appearance));
response.on_hover_ui_at_pointer(|ui| data_row_hover_ui(ui, obj, diffs, appearance));
response.context_menu(|ui| data_row_context_menu(ui, obj, diff_row, column, appearance));
response.on_hover_ui_at_pointer(|ui| data_row_hover_ui(ui, obj, diff_row, appearance));
}
}
pub(crate) fn split_diffs(
diffs: &[DataDiff],
reloc_diffs: &[DataRelocationDiff],
symbol_offset_in_section: usize,
) -> Vec<Vec<(DataDiff, Vec<DataRelocationDiff>)>> {
let mut split_diffs = Vec::<Vec<(DataDiff, Vec<DataRelocationDiff>)>>::new();
let mut row_diffs = Vec::<(DataDiff, Vec<DataRelocationDiff>)>::new();
// The offset shown on the side of the GUI, shifted by insertions/deletions.
let mut cur_addr = 0usize;
// The offset into the actual bytes of the section on this side, ignoring differences.
let mut cur_addr_actual = symbol_offset_in_section;
for diff in diffs {
let mut cur_len = 0usize;
while cur_len < diff.len {
let remaining_len = diff.len - cur_len;
let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW);
let len = min(remaining_len, remaining_in_row);
let data_diff = DataDiff {
data: if diff.data.is_empty() {
Vec::new()
} else {
diff.data[cur_len..cur_len + len].to_vec()
},
kind: diff.kind,
len,
symbol: String::new(), // TODO
};
let row_reloc_diffs: Vec<DataRelocationDiff> = if diff.data.is_empty() {
Vec::new()
} else {
let diff_range = cur_addr_actual + cur_len..cur_addr_actual + cur_len + len;
reloc_diffs
.iter()
.filter_map(|reloc_diff| {
if reloc_diff.range.start < diff_range.end
&& diff_range.start < reloc_diff.range.end
{
Some(reloc_diff.clone())
} else {
None
}
})
.collect()
};
let row_diff = (data_diff, row_reloc_diffs);
row_diffs.push(row_diff);
remaining_in_row -= len;
cur_len += len;
cur_addr += len;
if remaining_in_row == 0 {
split_diffs.push(take(&mut row_diffs));
}
}
cur_addr_actual += diff.data.len();
}
if !row_diffs.is_empty() {
split_diffs.push(take(&mut row_diffs));
}
split_diffs
}

View File

@@ -3,6 +3,7 @@ use objdiff_core::{
build::BuildStatus,
diff::{
DiffObjConfig, ObjectDiff, SymbolDiff,
data::BYTES_PER_ROW,
display::{ContextItem, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind},
},
obj::{Object, Symbol},
@@ -14,7 +15,7 @@ use crate::{
views::{
appearance::Appearance,
column_layout::{render_header, render_strips, render_table},
data_diff::{BYTES_PER_ROW, data_row_ui, split_diffs},
data_diff::data_row_ui,
extab_diff::extab_ui,
function_diff::{FunctionDiffContext, asm_col_ui},
symbol_diff::{
@@ -470,28 +471,14 @@ pub fn diff_view_ui(
{
// Joint diff view
hotkeys::check_scroll_hotkeys(ui, true);
let left_total_bytes =
left_symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
let right_total_bytes =
right_symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
if left_total_bytes != right_total_bytes {
ui.label("Data size mismatch");
let total_rows = left_symbol_diff.data_rows.len();
if total_rows != right_symbol_diff.data_rows.len() {
ui.label("Row count mismatch");
return;
}
if left_total_bytes == 0 {
if total_rows == 0 {
return;
}
let total_rows = (left_total_bytes - 1) / BYTES_PER_ROW + 1;
let left_diffs = split_diffs(
&left_symbol_diff.data_diff,
&left_symbol_diff.data_reloc_diff,
left_symbol.address as usize,
);
let right_diffs = split_diffs(
&right_symbol_diff.data_diff,
&right_symbol_diff.data_reloc_diff,
right_symbol.address as usize,
);
render_table(
ui,
available_width,
@@ -500,15 +487,15 @@ pub fn diff_view_ui(
total_rows,
|row, column| {
let i = row.index();
let row_offset = i * BYTES_PER_ROW;
let row_offset = i as u64 * BYTES_PER_ROW as u64;
row.col(|ui| {
if column == 0 {
data_row_ui(
ui,
Some(left_obj),
left_symbol.address as usize,
left_symbol.address,
row_offset,
&left_diffs[i],
&left_symbol_diff.data_rows[i],
appearance,
column,
);
@@ -516,9 +503,9 @@ pub fn diff_view_ui(
data_row_ui(
ui,
Some(right_obj),
right_symbol.address as usize,
right_symbol.address,
row_offset,
&right_diffs[i],
&right_symbol_diff.data_rows[i],
appearance,
column,
);
@@ -618,17 +605,10 @@ fn diff_col_ui(
extab_ui(ui, ctx, appearance, column);
} else if state.current_view == View::DataDiff {
hotkeys::check_scroll_hotkeys(ui, false);
let total_bytes =
symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
if total_bytes == 0 {
let total_rows = symbol_diff.data_rows.len();
if total_rows == 0 {
return ret;
}
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
let diffs = split_diffs(
&symbol_diff.data_diff,
&symbol_diff.data_reloc_diff,
symbol.address as usize,
);
render_table(
ui,
available_width / 2.0,
@@ -637,14 +617,14 @@ fn diff_col_ui(
total_rows,
|row, _column| {
let i = row.index();
let row_offset = i * BYTES_PER_ROW;
let row_offset = i as u64 * BYTES_PER_ROW as u64;
row.col(|ui| {
data_row_ui(
ui,
Some(obj),
symbol.address as usize,
symbol.address,
row_offset,
&diffs[i],
&symbol_diff.data_rows[i],
appearance,
column,
);