Remove alternate diff algorithms, only keep Patience

This commit is contained in:
Luke Street 2024-02-27 21:18:42 -07:00
parent 9a7d2bcebf
commit cff6a230a3
10 changed files with 42 additions and 697 deletions

42
Cargo.lock generated
View File

@ -110,9 +110,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.9" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom", "getrandom",
@ -1576,9 +1576,9 @@ dependencies = [
[[package]] [[package]]
name = "error-code" name = "error-code"
version = "3.1.0" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a147e1a6641a55d994b3e4e9fa4d9b180c8d652c09b363af8c9bf1b8e04139" checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
[[package]] [[package]]
name = "event-listener" name = "event-listener"
@ -2101,7 +2101,7 @@ dependencies = [
"presser", "presser",
"thiserror", "thiserror",
"winapi", "winapi",
"windows 0.51.1", "windows 0.52.0",
] ]
[[package]] [[package]]
@ -3004,7 +3004,6 @@ dependencies = [
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"similar", "similar",
"twox-hash",
] ]
[[package]] [[package]]
@ -3456,9 +3455,9 @@ checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.8.1" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
dependencies = [ dependencies = [
"either", "either",
"rayon-core", "rayon-core",
@ -4180,9 +4179,9 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.10.0" version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand 2.0.1", "fastrand 2.0.1",
@ -4507,17 +4506,6 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
[[package]]
name = "twox-hash"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if",
"rand",
"static_assertions",
]
[[package]] [[package]]
name = "type-map" name = "type-map"
version = "0.5.0" version = "0.5.0"
@ -5056,21 +5044,21 @@ dependencies = [
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.51.1" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [ dependencies = [
"windows-core", "windows-core",
"windows-targets 0.48.5", "windows-targets 0.52.3",
] ]
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.51.1" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.52.3",
] ]
[[package]] [[package]]

View File

@ -108,7 +108,7 @@ pub fn run(args: Args) -> Result<()> {
) )
}) })
.collect::<Result<Vec<Option<ReportUnit>>>>()?; .collect::<Result<Vec<Option<ReportUnit>>>>()?;
report.units = units.into_iter().flatten().collect::<Vec<ReportUnit>>(); report.units = units.into_iter().flatten().collect();
} }
for unit in &report.units { for unit in &report.units {
report.fuzzy_match_percent += unit.match_percent * unit.total_size as f32; report.fuzzy_match_percent += unit.match_percent * unit.total_size as f32;
@ -198,10 +198,14 @@ fn report_object(
continue; continue;
} }
} }
let match_percent = symbol.match_percent.unwrap_or(if object.complete == Some(true) { let match_percent = symbol.match_percent.unwrap_or_else(|| {
100.0 // Support cases where we don't have a target object,
} else { // assume complete means 100% match
0.0 if object.complete == Some(true) {
100.0
} else {
0.0
}
}); });
unit.match_percent += match_percent * symbol.size as f32; unit.match_percent += match_percent * symbol.size as f32;
unit.total_size += symbol.size; unit.total_size += symbol.size;

View File

@ -33,8 +33,7 @@ object = { version = "0.32.2", features = ["read_core", "std", "elf"], default-f
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618", optional = true } ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618", optional = true }
rabbitizer = { version = "1.8.1", optional = true } rabbitizer = { version = "1.8.1", optional = true }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
similar = "2.4.0" similar = { version = "2.4.0", default-features = false }
twox-hash = "1.6.3"
# config # config
globset = { version = "0.4.14", features = ["serde1"] } globset = { version = "0.4.14", features = ["serde1"] }

View File

@ -8,10 +8,7 @@ use anyhow::Result;
use similar::{capture_diff_slices_deadline, Algorithm}; use similar::{capture_diff_slices_deadline, Algorithm};
use crate::{ use crate::{
diff::{ diff::{DiffObjConfig, ProcessCodeResult},
editops::{editops_find, LevEditType},
DiffAlg, DiffObjConfig, ProcessCodeResult,
},
obj, obj,
obj::{ obj::{
ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
@ -101,45 +98,7 @@ pub fn diff_code(
let mut left_diff = Vec::<ObjInsDiff>::new(); let mut left_diff = Vec::<ObjInsDiff>::new();
let mut right_diff = Vec::<ObjInsDiff>::new(); let mut right_diff = Vec::<ObjInsDiff>::new();
match config.code_alg { diff_instructions(&mut left_diff, &mut right_diff, &left_out, &right_out)?;
DiffAlg::Levenshtein => {
diff_instructions_lev(
&mut left_diff,
&mut right_diff,
left_symbol,
right_symbol,
&left_out,
&right_out,
)?;
}
DiffAlg::Lcs => {
diff_instructions_similar(
Algorithm::Lcs,
&mut left_diff,
&mut right_diff,
&left_out,
&right_out,
)?;
}
DiffAlg::Myers => {
diff_instructions_similar(
Algorithm::Myers,
&mut left_diff,
&mut right_diff,
&left_out,
&right_out,
)?;
}
DiffAlg::Patience => {
diff_instructions_similar(
Algorithm::Patience,
&mut left_diff,
&mut right_diff,
&left_out,
&right_out,
)?;
}
}
resolve_branches(&mut left_diff); resolve_branches(&mut left_diff);
resolve_branches(&mut right_diff); resolve_branches(&mut right_diff);
@ -168,15 +127,19 @@ pub fn diff_code(
Ok(()) Ok(())
} }
fn diff_instructions_similar( fn diff_instructions(
alg: Algorithm,
left_diff: &mut Vec<ObjInsDiff>, left_diff: &mut Vec<ObjInsDiff>,
right_diff: &mut Vec<ObjInsDiff>, right_diff: &mut Vec<ObjInsDiff>,
left_code: &ProcessCodeResult, left_code: &ProcessCodeResult,
right_code: &ProcessCodeResult, right_code: &ProcessCodeResult,
) -> Result<()> { ) -> Result<()> {
let deadline = Instant::now() + Duration::from_secs(5); let deadline = Instant::now() + Duration::from_secs(5);
let ops = capture_diff_slices_deadline(alg, &left_code.ops, &right_code.ops, Some(deadline)); let ops = capture_diff_slices_deadline(
Algorithm::Patience,
&left_code.ops,
&right_code.ops,
Some(deadline),
);
if ops.is_empty() { if ops.is_empty() {
left_diff.extend( left_diff.extend(
left_code left_code
@ -217,75 +180,6 @@ fn diff_instructions_similar(
Ok(()) Ok(())
} }
fn diff_instructions_lev(
left_diff: &mut Vec<ObjInsDiff>,
right_diff: &mut Vec<ObjInsDiff>,
left_symbol: &ObjSymbol,
right_symbol: &ObjSymbol,
left_code: &ProcessCodeResult,
right_code: &ProcessCodeResult,
) -> Result<()> {
let edit_ops = editops_find(&left_code.ops, &right_code.ops);
let mut op_iter = edit_ops.iter();
let mut left_iter = left_code.insts.iter();
let mut right_iter = right_code.insts.iter();
let mut cur_op = op_iter.next();
let mut cur_left = left_iter.next();
let mut cur_right = right_iter.next();
while let Some(op) = cur_op {
let left_addr = op.first_start as u32 * 4;
let right_addr = op.second_start as u32 * 4;
while let (Some(left), Some(right)) = (cur_left, cur_right) {
if (left.address - left_symbol.address as u32) < left_addr {
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
right_diff.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
} else {
break;
}
cur_left = left_iter.next();
cur_right = right_iter.next();
}
if let (Some(left), Some(right)) = (cur_left, cur_right) {
debug_assert_eq!(left.address - left_symbol.address as u32, left_addr);
debug_assert_eq!(right.address - right_symbol.address as u32, right_addr);
match op.op_type {
LevEditType::Replace => {
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
right_diff
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
cur_left = left_iter.next();
cur_right = right_iter.next();
}
LevEditType::Insert => {
left_diff.push(ObjInsDiff::default());
right_diff
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
cur_right = right_iter.next();
}
LevEditType::Delete => {
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
right_diff.push(ObjInsDiff::default());
cur_left = left_iter.next();
}
}
} else {
break;
}
cur_op = op_iter.next();
}
// Finalize
while cur_left.is_some() || cur_right.is_some() {
left_diff.push(ObjInsDiff { ins: cur_left.cloned(), ..ObjInsDiff::default() });
right_diff.push(ObjInsDiff { ins: cur_right.cloned(), ..ObjInsDiff::default() });
cur_left = left_iter.next();
cur_right = right_iter.next();
}
Ok(())
}
fn resolve_branches(vec: &mut [ObjInsDiff]) { fn resolve_branches(vec: &mut [ObjInsDiff]) {
let mut branch_idx = 0usize; let mut branch_idx = 0usize;
// Map addresses to indices // Map addresses to indices

View File

@ -1,28 +1,12 @@
use std::{ use std::{
cmp::{max, min, Ordering}, cmp::{max, min, Ordering},
mem::take,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use anyhow::{ensure, Result}; use anyhow::Result;
use similar::{capture_diff_slices_deadline, Algorithm}; use similar::{capture_diff_slices_deadline, Algorithm};
use crate::{ use crate::obj::{ObjDataDiff, ObjDataDiffKind, ObjSection, ObjSymbol};
diff::{
editops::{editops_find, LevEditType},
DiffAlg,
},
obj::{ObjDataDiff, ObjDataDiffKind, ObjSection, ObjSymbol},
};
pub fn diff_data(alg: DiffAlg, left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
match alg {
DiffAlg::Levenshtein => diff_data_lev(left, right),
DiffAlg::Lcs => diff_data_similar(Algorithm::Lcs, left, right),
DiffAlg::Myers => diff_data_similar(Algorithm::Myers, left, right),
DiffAlg::Patience => diff_data_similar(Algorithm::Patience, left, right),
}
}
pub fn diff_bss_symbols( pub fn diff_bss_symbols(
left_symbols: &mut [ObjSymbol], left_symbols: &mut [ObjSymbol],
@ -40,67 +24,10 @@ pub fn diff_bss_symbols(
Ok(()) Ok(())
} }
// WIP diff-by-symbol pub fn diff_data(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
#[allow(dead_code)]
pub fn diff_data_symbols(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
let mut left_ops = Vec::<u32>::with_capacity(left.symbols.len());
let mut right_ops = Vec::<u32>::with_capacity(right.symbols.len());
for left_symbol in &left.symbols {
let data = &left.data
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
let hash = twox_hash::xxh3::hash64(data);
left_ops.push(hash as u32);
}
for symbol in &right.symbols {
let data = &right.data[symbol.address as usize..(symbol.address + symbol.size) as usize];
let hash = twox_hash::xxh3::hash64(data);
right_ops.push(hash as u32);
}
let edit_ops = editops_find(&left_ops, &right_ops);
if edit_ops.is_empty() && !left.data.is_empty() {
let mut left_iter = left.symbols.iter_mut();
let mut right_iter = right.symbols.iter_mut();
loop {
let (left_symbol, right_symbol) = match (left_iter.next(), right_iter.next()) {
(Some(l), Some(r)) => (l, r),
(None, None) => break,
_ => return Err(anyhow::Error::msg("L/R mismatch in diff_data_symbols")),
};
let left_data = &left.data
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
let right_data = &right.data[right_symbol.address as usize
..(right_symbol.address + right_symbol.size) as usize];
left.data_diff.push(ObjDataDiff {
data: left_data.to_vec(),
kind: ObjDataDiffKind::None,
len: left_symbol.size as usize,
symbol: left_symbol.name.clone(),
});
right.data_diff.push(ObjDataDiff {
data: right_data.to_vec(),
kind: ObjDataDiffKind::None,
len: right_symbol.size as usize,
symbol: right_symbol.name.clone(),
});
left_symbol.diff_symbol = Some(right_symbol.name.clone());
left_symbol.match_percent = Some(100.0);
right_symbol.diff_symbol = Some(left_symbol.name.clone());
right_symbol.match_percent = Some(100.0);
}
return Ok(());
}
Ok(())
}
pub fn diff_data_similar(
alg: Algorithm,
left: &mut ObjSection,
right: &mut ObjSection,
) -> Result<()> {
let deadline = Instant::now() + Duration::from_secs(5); let deadline = Instant::now() + Duration::from_secs(5);
let ops = capture_diff_slices_deadline(alg, &left.data, &right.data, Some(deadline)); let ops =
capture_diff_slices_deadline(Algorithm::Patience, &left.data, &right.data, Some(deadline));
let mut left_diff = Vec::<ObjDataDiff>::new(); let mut left_diff = Vec::<ObjDataDiff>::new();
let mut right_diff = Vec::<ObjDataDiff>::new(); let mut right_diff = Vec::<ObjDataDiff>::new();
@ -175,226 +102,6 @@ pub fn diff_data_similar(
Ok(()) Ok(())
} }
pub fn diff_data_lev(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
let matrix_size = (left.data.len() as u64).saturating_mul(right.data.len() as u64);
ensure!(
matrix_size < 1_000_000_000,
"Data section {} too large for Levenshtein diff ({} * {} = {})",
left.name,
left.data.len(),
right.data.len(),
matrix_size
);
let edit_ops = editops_find(&left.data, &right.data);
if edit_ops.is_empty() && !left.data.is_empty() {
left.data_diff = vec![ObjDataDiff {
data: left.data.clone(),
kind: ObjDataDiffKind::None,
len: left.data.len(),
symbol: String::new(),
}];
right.data_diff = vec![ObjDataDiff {
data: right.data.clone(),
kind: ObjDataDiffKind::None,
len: right.data.len(),
symbol: String::new(),
}];
return Ok(());
}
let mut left_diff = Vec::<ObjDataDiff>::new();
let mut right_diff = Vec::<ObjDataDiff>::new();
let mut left_cur = 0usize;
let mut right_cur = 0usize;
let mut cur_op = LevEditType::Replace;
let mut cur_left_data = Vec::<u8>::new();
let mut cur_right_data = Vec::<u8>::new();
for op in edit_ops {
if cur_op != op.op_type || left_cur < op.first_start || right_cur < op.second_start {
match cur_op {
LevEditType::Replace => {
let left_data = take(&mut cur_left_data);
let right_data = take(&mut cur_right_data);
let left_data_len = left_data.len();
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Replace,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Replace,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Insert => {
let right_data = take(&mut cur_right_data);
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Delete => {
let left_data = take(&mut cur_left_data);
let left_data_len = left_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
}
}
}
if left_cur < op.first_start {
left_diff.push(ObjDataDiff {
data: left.data[left_cur..op.first_start].to_vec(),
kind: ObjDataDiffKind::None,
len: op.first_start - left_cur,
symbol: String::new(),
});
left_cur = op.first_start;
}
if right_cur < op.second_start {
right_diff.push(ObjDataDiff {
data: right.data[right_cur..op.second_start].to_vec(),
kind: ObjDataDiffKind::None,
len: op.second_start - right_cur,
symbol: String::new(),
});
right_cur = op.second_start;
}
match op.op_type {
LevEditType::Replace => {
cur_left_data.push(left.data[left_cur]);
cur_right_data.push(right.data[right_cur]);
left_cur += 1;
right_cur += 1;
}
LevEditType::Insert => {
cur_right_data.push(right.data[right_cur]);
right_cur += 1;
}
LevEditType::Delete => {
cur_left_data.push(left.data[left_cur]);
left_cur += 1;
}
}
cur_op = op.op_type;
}
// if left_cur < left.data.len() {
// let len = left.data.len() - left_cur;
// left_diff.push(ObjDataDiff {
// data: left.data[left_cur..].to_vec(),
// kind: ObjDataDiffKind::Delete,
// len,
// });
// right_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Delete, len });
// } else if right_cur < right.data.len() {
// let len = right.data.len() - right_cur;
// left_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Insert, len });
// right_diff.push(ObjDataDiff {
// data: right.data[right_cur..].to_vec(),
// kind: ObjDataDiffKind::Insert,
// len,
// });
// }
// TODO: merge with above
match cur_op {
LevEditType::Replace => {
let left_data = take(&mut cur_left_data);
let right_data = take(&mut cur_right_data);
let left_data_len = left_data.len();
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Replace,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Replace,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Insert => {
let right_data = take(&mut cur_right_data);
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Delete => {
let left_data = take(&mut cur_left_data);
let left_data_len = left_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
}
}
if left_cur < left.data.len() {
left_diff.push(ObjDataDiff {
data: left.data[left_cur..].to_vec(),
kind: ObjDataDiffKind::None,
len: left.data.len() - left_cur,
symbol: String::new(),
});
}
if right_cur < right.data.len() {
right_diff.push(ObjDataDiff {
data: right.data[right_cur..].to_vec(),
kind: ObjDataDiffKind::None,
len: right.data.len() - right_cur,
symbol: String::new(),
});
}
left.data_diff = left_diff;
right.data_diff = right_diff;
Ok(())
}
pub fn no_diff_data(section: &mut ObjSection) { pub fn no_diff_data(section: &mut ObjSection) {
section.data_diff = vec![ObjDataDiff { section.data_diff = vec![ObjDataDiff {
data: section.data.clone(), data: section.data.clone(),

View File

@ -1,162 +0,0 @@
/// Adapted from https://crates.io/crates/rapidfuzz
// Copyright 2020 maxbachmann
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum LevEditType {
Replace,
Insert,
Delete,
}
#[derive(Debug, PartialEq, Eq)]
pub struct LevEditOp {
pub op_type: LevEditType, /* editing operation type */
pub first_start: usize, /* source block position */
pub second_start: usize, /* destination position */
}
pub fn editops_find<T>(query: &[T], choice: &[T]) -> Vec<LevEditOp>
where T: PartialEq {
let Affix { prefix_len, suffix_len } = Affix::find(query, choice);
let first_string = &query[prefix_len..query.len() - suffix_len];
let second_string = &choice[prefix_len..choice.len() - suffix_len];
let matrix_columns = first_string.len() + 1;
let matrix_rows = second_string.len() + 1;
// TODO maybe use an actual matrix for readability
let mut cache_matrix: Vec<usize> = vec![0; matrix_rows * matrix_columns];
for (i, elem) in cache_matrix.iter_mut().enumerate().take(matrix_rows) {
*elem = i;
}
for i in 1..matrix_columns {
cache_matrix[matrix_rows * i] = i;
}
for (i, char1) in first_string.iter().enumerate() {
let mut prev = i * matrix_rows;
let current = prev + matrix_rows;
let mut x = i + 1;
for (p, char2p) in second_string.iter().enumerate() {
let mut c3 = cache_matrix[prev] + (char1 != char2p) as usize;
prev += 1;
x += 1;
if x >= c3 {
x = c3;
}
c3 = cache_matrix[prev] + 1;
if x > c3 {
x = c3;
}
cache_matrix[current + 1 + p] = x;
}
}
editops_from_cost_matrix(matrix_columns, matrix_rows, prefix_len, cache_matrix)
}
fn editops_from_cost_matrix(
len1: usize,
len2: usize,
prefix_len: usize,
cache_matrix: Vec<usize>,
) -> Vec<LevEditOp> {
let mut ops = Vec::with_capacity(cache_matrix[len1 * len2 - 1]);
let mut dir = 0;
let mut i = len1 - 1;
let mut j = len2 - 1;
let mut p = len1 * len2 - 1;
//TODO this is still pretty ugly
while i > 0 || j > 0 {
let current_value = cache_matrix[p];
// More than one operation can be possible at a time. We use `dir` to
// decide when ambiguous.
let is_insert = j > 0 && current_value == cache_matrix[p - 1] + 1;
let is_delete = i > 0 && current_value == cache_matrix[p - len2] + 1;
let is_replace = i > 0 && j > 0 && current_value == cache_matrix[p - len2 - 1] + 1;
let (op_type, new_dir) = match (dir, is_insert, is_delete, is_replace) {
(_, false, false, false) => (None, 0),
(-1, true, _, _) => (Some(LevEditType::Insert), -1),
(1, _, true, _) => (Some(LevEditType::Delete), 1),
(_, _, _, true) => (Some(LevEditType::Replace), 0),
(0, true, _, _) => (Some(LevEditType::Insert), -1),
(0, _, true, _) => (Some(LevEditType::Delete), 1),
_ => panic!("something went terribly wrong"),
};
match new_dir {
-1 => {
j -= 1;
p -= 1;
}
1 => {
i -= 1;
p -= len2;
}
0 => {
i -= 1;
j -= 1;
p -= len2 + 1;
}
_ => panic!("something went terribly wrong"),
};
dir = new_dir;
if let Some(op_type) = op_type {
ops.insert(0, LevEditOp {
op_type,
first_start: i + prefix_len,
second_start: j + prefix_len,
});
}
}
ops
}
pub struct Affix {
pub prefix_len: usize,
pub suffix_len: usize,
}
impl Affix {
pub fn find<T>(s1: &[T], s2: &[T]) -> Affix
where T: PartialEq {
let prefix_len = s1.iter().zip(s2.iter()).take_while(|t| t.0 == t.1).count();
let suffix_len = s1[prefix_len..]
.iter()
.rev()
.zip(s2[prefix_len..].iter().rev())
.take_while(|t| t.0 == t.1)
.count();
Affix { prefix_len, suffix_len }
}
}

View File

@ -1,10 +1,8 @@
pub mod code; pub mod code;
pub mod data; pub mod data;
pub mod display; pub mod display;
pub mod editops;
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
diff::{ diff::{
@ -14,19 +12,8 @@ use crate::{
obj::{ObjInfo, ObjIns, ObjSectionKind}, obj::{ObjInfo, ObjIns, ObjSectionKind},
}; };
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum DiffAlg {
#[default]
Patience,
Levenshtein,
Myers,
Lcs,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)] #[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct DiffObjConfig { pub struct DiffObjConfig {
pub code_alg: DiffAlg,
pub data_alg: DiffAlg,
pub relax_reloc_diffs: bool, pub relax_reloc_diffs: bool,
} }
@ -80,7 +67,7 @@ pub fn diff_objs(
.and_then(|obj| obj.sections.iter_mut().find(|s| s.name == left_section.name)) .and_then(|obj| obj.sections.iter_mut().find(|s| s.name == left_section.name))
{ {
if left_section.kind == ObjSectionKind::Data { if left_section.kind == ObjSectionKind::Data {
diff_data(config.data_alg, left_section, right_section)?; diff_data(left_section, right_section)?;
} else if left_section.kind == ObjSectionKind::Bss { } else if left_section.kind == ObjSectionKind::Bss {
diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?; diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?;
} }

View File

@ -106,10 +106,6 @@ pub struct AppConfig {
#[serde(default)] #[serde(default)]
pub recent_projects: Vec<PathBuf>, pub recent_projects: Vec<PathBuf>,
#[serde(default)] #[serde(default)]
pub code_alg: DiffAlg,
#[serde(default)]
pub data_alg: DiffAlg,
#[serde(default)]
pub relax_reloc_diffs: bool, pub relax_reloc_diffs: bool,
#[serde(skip)] #[serde(skip)]
@ -148,8 +144,6 @@ impl Default for AppConfig {
auto_update_check: true, auto_update_check: true,
watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(), watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
recent_projects: vec![], recent_projects: vec![],
code_alg: Default::default(),
data_alg: Default::default(),
relax_reloc_diffs: false, relax_reloc_diffs: false,
objects: vec![], objects: vec![],
object_nodes: vec![], object_nodes: vec![],

View File

@ -57,8 +57,6 @@ pub struct ObjDiffConfig {
pub build_base: bool, pub build_base: bool,
pub build_target: bool, pub build_target: bool,
pub selected_obj: Option<ObjectConfig>, pub selected_obj: Option<ObjectConfig>,
pub code_alg: DiffAlg,
pub data_alg: DiffAlg,
pub relax_reloc_diffs: bool, pub relax_reloc_diffs: bool,
} }
@ -69,8 +67,6 @@ impl ObjDiffConfig {
build_base: config.build_base, build_base: config.build_base,
build_target: config.build_target, build_target: config.build_target,
selected_obj: config.selected_obj.clone(), selected_obj: config.selected_obj.clone(),
code_alg: config.code_alg,
data_alg: config.data_alg,
relax_reloc_diffs: config.relax_reloc_diffs, relax_reloc_diffs: config.relax_reloc_diffs,
} }
} }
@ -253,11 +249,7 @@ fn run_build(
}; };
update_status(context, "Performing diff".to_string(), 4, total, &cancel)?; update_status(context, "Performing diff".to_string(), 4, total, &cancel)?;
let diff_config = DiffObjConfig { let diff_config = DiffObjConfig { relax_reloc_diffs: config.relax_reloc_diffs };
code_alg: config.code_alg,
data_alg: config.data_alg,
relax_reloc_diffs: config.relax_reloc_diffs,
};
diff_objs(&diff_config, first_obj.as_mut(), second_obj.as_mut())?; diff_objs(&diff_config, first_obj.as_mut(), second_obj.as_mut())?;
update_status(context, "Complete".to_string(), total, total, &cancel)?; update_status(context, "Complete".to_string(), total, total, &cancel)?;

View File

@ -14,10 +14,7 @@ use egui::{
SelectableLabel, TextFormat, Widget, WidgetText, SelectableLabel, TextFormat, Widget, WidgetText,
}; };
use globset::Glob; use globset::Glob;
use objdiff_core::{ use objdiff_core::config::{ProjectObject, DEFAULT_WATCH_PATTERNS};
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
diff::DiffAlg,
};
use self_update::cargo_crate_version; use self_update::cargo_crate_version;
use crate::{ use crate::{
@ -853,58 +850,3 @@ pub fn diff_options_window(
diff_options_ui(ui, &mut config_guard, appearance); diff_options_ui(ui, &mut config_guard, appearance);
}); });
} }
fn diff_options_ui(ui: &mut egui::Ui, config: &mut AppConfig, appearance: &Appearance) {
let mut job = LayoutJob::default();
job.append(
"Current default: ",
0.0,
TextFormat::simple(appearance.ui_font.clone(), appearance.text_color),
);
job.append(
diff_alg_to_string(DiffAlg::default()),
0.0,
TextFormat::simple(appearance.ui_font.clone(), appearance.emphasized_text_color),
);
ui.label(job);
let mut job = LayoutJob::default();
job.append(
"Previous default: ",
0.0,
TextFormat::simple(appearance.ui_font.clone(), appearance.text_color),
);
job.append(
"Levenshtein",
0.0,
TextFormat::simple(appearance.ui_font.clone(), appearance.emphasized_text_color),
);
ui.label(job);
ui.label("Please provide feedback!");
if diff_alg_ui(ui, "Code diff algorithm", &mut config.code_alg) {
config.queue_reload = true;
}
if diff_alg_ui(ui, "Data diff algorithm", &mut config.data_alg) {
config.queue_reload = true;
}
}
fn diff_alg_ui(ui: &mut egui::Ui, label: impl Into<WidgetText>, alg: &mut DiffAlg) -> bool {
let response = egui::ComboBox::from_label(label)
.selected_text(diff_alg_to_string(*alg))
.show_ui(ui, |ui| {
ui.selectable_value(alg, DiffAlg::Patience, "Patience").changed()
| ui.selectable_value(alg, DiffAlg::Levenshtein, "Levenshtein").changed()
| ui.selectable_value(alg, DiffAlg::Myers, "Myers").changed()
| ui.selectable_value(alg, DiffAlg::Lcs, "LCS").changed()
});
response.inner.unwrap_or(false)
}
const fn diff_alg_to_string(alg: DiffAlg) -> &'static str {
match alg {
DiffAlg::Patience => "Patience",
DiffAlg::Levenshtein => "Levenshtein",
DiffAlg::Lcs => "LCS",
DiffAlg::Myers => "Myers",
}
}