use std::{collections::BTreeMap, mem, ops::Deref};
use crate::{
analysis::{self, SymbolMap},
index::{IdMap, SymbolId},
};
pub struct SymbolMapping {
left_to_right: IdMap<SymbolId, SymbolId>,
left_non_matched: Vec<SymbolId>,
right_non_matched: Vec<SymbolId>,
}
impl SymbolMapping {
pub fn mapped_list(&self) -> impl Iterator<Item = (SymbolId, SymbolId)> + use<'_> {
self.left_to_right
.iter()
.map(|(right, left)| (right, *left))
}
pub fn map(&self, left: SymbolId) -> Option<SymbolId> {
self.left_to_right.get(left).copied()
}
pub fn left_only(&self) -> impl Iterator<Item = SymbolId> + use<'_> {
self.left_non_matched.iter().copied()
}
pub fn right_only(&self) -> impl Iterator<Item = SymbolId> + use<'_> {
self.right_non_matched.iter().copied()
}
}
type SVec<T, const SIZE: usize = 4> = smallvec::SmallVec<[T; SIZE]>;
pub struct Differ<L, R> {
left: L,
right: R,
}
impl<'left, 'right, L, R> Differ<L, R>
where
L: SymbolMapWithContent<'left>,
R: SymbolMapWithContent<'right>,
{
pub fn new(left: L, right: R) -> Self {
Self { left, right }
}
pub fn symbol_map(&self) -> SymbolMapping {
let mut mapping = self.build_name_mapping();
self.try_match_wamex_split_point(&mut mapping);
self.refine_mapping(&mut mapping);
mapping
}
fn is_anon_name(name: &str) -> bool {
name.starts_with(".L") || name.starts_with("$L")
}
fn build_name_mapping(&self) -> SymbolMapping {
let mut name_to_left_symbol: BTreeMap<&str, SymbolId> = BTreeMap::new();
let mut duplicate_left: BTreeMap<&str, SVec<SymbolId>> = BTreeMap::new();
for (sym_id, symbol) in self.left.symbols().iter() {
if let Some(name) = &symbol.linking_name {
if Self::is_anon_name(name) {
continue;
}
if let Some(prev) = name_to_left_symbol.insert(name, sym_id) {
duplicate_left.entry(name).or_default().push(prev);
}
}
}
let duplicate_names: SVec<_, 16> = duplicate_left.keys().cloned().collect();
for name in duplicate_names {
let last = name_to_left_symbol.remove(name).unwrap();
let duplicates = duplicate_left.get_mut(name).unwrap();
duplicates.push(last);
}
let mut mapping = IdMap::new();
let mut non_matched_right_symbols: Vec<SymbolId> = Vec::new();
let mut dups: SVec<_, 16> = SVec::new();
for (right_sym_id, right_symbol) in self.right.symbols().iter() {
if let Some(name) = &right_symbol.linking_name
&& !Self::is_anon_name(name)
{
if let Some(&left_sym_id) = name_to_left_symbol.get(name.deref()) {
if let Some(dup) = mapping.insert(left_sym_id, right_sym_id) {
dups.push(left_sym_id);
non_matched_right_symbols.push(dup);
}
continue;
}
}
non_matched_right_symbols.push(right_sym_id);
}
for dup in dups {
let right = mapping.remove(dup).unwrap();
non_matched_right_symbols.push(right);
}
let non_matched_left_symbols: Vec<SymbolId> = self
.left
.symbols()
.iter()
.filter_map(|(left_sym_id, _)| {
if mapping.get(left_sym_id).is_none() {
Some(left_sym_id)
} else {
None
}
})
.collect();
SymbolMapping {
left_to_right: mapping,
left_non_matched: non_matched_left_symbols,
right_non_matched: non_matched_right_symbols,
}
}
fn wamex_parse_name(name: &str) -> Option<(&str, &str)> {
use analysis::split_point::{
SPLIT_EXPORT_POSTFIX, SPLIT_IMPORT_POSTFIX, WAMEX_ENTRY_PREFIX, parser,
};
if let Some(v) = parser(name, WAMEX_ENTRY_PREFIX, SPLIT_IMPORT_POSTFIX) {
return Some(v);
};
parser(name, WAMEX_ENTRY_PREFIX, SPLIT_EXPORT_POSTFIX)
}
fn try_match_wamex_split_point(&self, mapping: &mut SymbolMapping) {
let mut non_matched_wamex_left_symbols: BTreeMap<&str, SVec<(&str, SymbolId)>> =
BTreeMap::new();
let mut non_matched_wamex_right_symbols: BTreeMap<&str, SVec<(&str, SymbolId)>> =
BTreeMap::new();
for left in &mapping.left_non_matched {
let left_symbol = &self.left.symbols().get(*left).unwrap();
let Some(name) = &left_symbol.linking_name else {
continue;
};
if !name.contains(analysis::split_point::WAMEX_ENTRY_PREFIX) {
continue;
}
let Some((module, fn_name)) = Self::wamex_parse_name(name) else {
continue;
};
non_matched_wamex_left_symbols
.entry(module)
.or_default()
.push((fn_name, *left));
}
for right in &mapping.right_non_matched {
let right_symbol = &self.right.symbols().get(*right).unwrap();
let Some(name) = &right_symbol.linking_name else {
continue;
};
if !name.contains(analysis::split_point::WAMEX_ENTRY_PREFIX) {
continue;
}
let Some((module, fn_name)) = Self::wamex_parse_name(name) else {
continue;
};
non_matched_wamex_right_symbols
.entry(module)
.or_default()
.push((fn_name, *right));
}
for (module, mut left_syms) in non_matched_wamex_left_symbols {
let Some(mut right_syms) = non_matched_wamex_right_symbols.remove(module) else {
continue;
};
left_syms.sort_by_key(|(fn_name, _)| *fn_name);
right_syms.sort_by_key(|(fn_name, _)| *fn_name);
for (left, right) in left_syms.into_iter().zip(right_syms.into_iter()) {
log::info!(
"Matched wamex split point symbol: module: {module}, left: {:?}, right: {:?}",
left.0,
right.0
);
mapping.left_to_right.insert(left.1, right.1);
mapping.left_non_matched.retain(|v| *v != left.1);
mapping.right_non_matched.retain(|v| *v != right.1);
}
}
}
fn is_same_content(
&self,
mapping: &SymbolMapping,
left_sym_id: SymbolId,
right_sym_id: SymbolId,
) -> Result<(), ReplaceDetail> {
let left_symbol = &self.left.symbols().get(left_sym_id).unwrap();
let right_symbol = &self.right.symbols().get(right_sym_id).unwrap();
let left_content = self.left.stable_content(left_sym_id);
let right_content = self.right.stable_content(right_sym_id);
if left_content != right_content {
return Err(ReplaceDetail::BodyChanged);
}
let right_childs = right_symbol.childs().collect::<Vec<_>>();
let mut left_childs: Vec<SymbolId> = Vec::new();
for left_sym in left_symbol.childs() {
let Some(&right_sym_mapped) = mapping.left_to_right.get(left_sym) else {
return Err(ReplaceDetail::UnresolvedChildren(left_sym));
};
left_childs.push(right_sym_mapped);
}
if left_childs.len() != right_childs.len() {
return Err(ReplaceDetail::ChildrenChanged);
}
Ok(())
}
fn refine_mapping(&self, mapping: &mut SymbolMapping) {
let mut left_parent_map: BTreeMap<SymbolId, SymbolContext> = BTreeMap::new();
for (sym_id, symbol) in self.left.symbols().iter() {
for child in symbol.childs() {
left_parent_map
.entry(child)
.or_default()
.parents
.push(sym_id);
}
}
let mut right_parent_map: BTreeMap<SymbolId, SymbolContext> = BTreeMap::new();
for (sym_id, symbol) in self.right.symbols().iter() {
for child in symbol.childs() {
right_parent_map
.entry(child)
.or_default()
.parents
.push(sym_id);
}
}
let mut right_candidates = BTreeMap::<_, SVec<_>>::new();
for right_sym in std::mem::take(&mut mapping.right_non_matched) {
let right_symbol = &self.right.symbols().get(right_sym).unwrap();
let context = right_parent_map
.get(&right_sym)
.cloned()
.unwrap_or_default();
let key = SymbolKey {
stable_name: right_symbol.stable_name(),
};
let entry = right_candidates.entry(key).or_default();
entry.push(SymbolWithContext {
symbol: right_sym,
context,
});
}
let mut queue = std::mem::take(&mut mapping.left_non_matched);
loop {
let queue_len = queue.len();
let mut left_candidates = BTreeMap::<_, SVec<_>>::new();
for left_sym in std::mem::take(&mut queue) {
let left_mapped_candidate_key = {
let left_symbol = &self.left.symbols().get(left_sym).unwrap();
SymbolKey {
stable_name: left_symbol.stable_name(),
}
};
let mut context = left_parent_map.get(&left_sym).cloned().unwrap_or_default();
let parents: Option<SVec<_>> = std::mem::take(&mut context.parents)
.into_iter()
.map(|parent| mapping.left_to_right.get(parent).copied())
.collect();
let Some(parents) = parents else {
queue.push(left_sym);
continue;
};
context.parents = parents;
left_candidates
.entry(left_mapped_candidate_key)
.or_default()
.push(SymbolWithContext {
symbol: left_sym,
context,
});
}
for (key, mut left_syms) in left_candidates {
let Some(mut right_syms) = right_candidates.remove(&key) else {
for s in left_syms {
mapping.left_non_matched.push(s.symbol);
}
continue;
};
mapping.match_list_by_context(&mut left_syms, &mut right_syms);
if !right_syms.is_empty() {
assert!(right_candidates.insert(key, right_syms).is_none());
}
for s in left_syms {
queue.push(s.symbol);
}
}
if queue.is_empty() || queue.len() == queue_len {
break;
}
}
for (_, right_syms) in right_candidates {
for s in right_syms {
mapping.right_non_matched.push(s.symbol);
}
}
for left_sym in queue {
mapping.left_non_matched.push(left_sym);
}
}
pub fn build_diff(&self, mapping: &SymbolMapping) -> DiffResult {
let mut diff_result = DiffResult::new();
for (left_sym_id, right_sym_id) in mapping.mapped_list() {
match self.is_same_content(mapping, left_sym_id, right_sym_id) {
Ok(()) => diff_result.push_same(left_sym_id, right_sym_id),
Err(detail) => diff_result.push_replaced(left_sym_id, right_sym_id, detail),
}
}
for left_sym_id in mapping.left_only() {
diff_result.push_removed(left_sym_id);
}
for right_sym_id in mapping.right_only() {
diff_result.push_added(right_sym_id);
}
diff_result
}
fn left_sym_name<'a>(&'a self, sym_id: SymbolId) -> Option<&'a str>
where
'left: 'a,
{
self.left.symbols().get(sym_id).map(|s| &*s.name)
}
fn right_sym_name<'a>(&'a self, sym_id: SymbolId) -> Option<&'a str>
where
'right: 'a,
{
self.right.symbols().get(sym_id).map(|s| &*s.name)
}
pub fn debug_diff(&self, diff: &DiffResult) {
let replaced_iter = diff.replaced();
let added_iter = diff.added();
let removed_iter = diff.removed();
println!(
"Replaced: {}, Added: {}, Removed: {}, Same: {}",
replaced_iter.clone().count(),
added_iter.clone().count(),
removed_iter.clone().count(),
diff.same().count()
);
for entry in added_iter {
let DiffEntry::Added { right } = entry else {
continue;
};
let name = self.right_sym_name(*right).unwrap_or("<unknown>");
println!("Added: {name} [{index}]", index = right);
}
for entry in removed_iter {
let DiffEntry::Removed { left } = entry else {
continue;
};
let name = self.left_sym_name(*left).unwrap_or("<unknown>");
println!("Removed: {name} [{index}]", index = left);
}
for entry in replaced_iter {
let DiffEntry::Replaced {
left,
right,
detail,
} = entry
else {
continue;
};
let left_name = self.left_sym_name(*left).unwrap_or("<unknown>");
let right_name = self.right_sym_name(*right).unwrap_or("<unknown>");
let detail = match detail {
ReplaceDetail::BodyChanged => {
let left_content = self.left.stable_content(*left).unwrap_or_default();
let right_content = self.right.stable_content(*right).unwrap_or_default();
format_args!(
"Body changed from {left_content} to {right_content}",
left_content = hex::encode(left_content),
right_content = hex::encode(right_content)
)
}
ReplaceDetail::ChildrenChanged => {
format_args!("Children changed")
}
ReplaceDetail::UnresolvedChildren(v) => {
format_args!("Unresolved child symbol id: {v}", v = *v)
}
};
println!(
"Replaced: {left_name} [{left_index}] -> {right_name} [{right_index}] Detail: {detail}",
left_index = left,
right_index = right
);
}
}
}
#[derive(Debug, Clone)]
pub struct DiffResult {
entries: Vec<DiffEntry>,
}
impl FromIterator<DiffEntry> for DiffResult {
fn from_iter<T: IntoIterator<Item = DiffEntry>>(iter: T) -> Self {
let mut diff_result = DiffResult::new();
for entry in iter {
diff_result.push(entry);
}
diff_result
}
}
impl DiffResult {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn push(&mut self, entry: DiffEntry) {
self.entries.push(entry);
}
pub fn push_added(&mut self, right: SymbolId) {
self.entries.push(DiffEntry::Added { right });
}
pub fn push_removed(&mut self, left: SymbolId) {
self.entries.push(DiffEntry::Removed { left });
}
pub fn push_replaced(&mut self, left: SymbolId, right: SymbolId, detail: ReplaceDetail) {
self.entries.push(DiffEntry::Replaced {
left,
right,
detail,
});
}
pub fn push_same(&mut self, left: SymbolId, right: SymbolId) {
self.entries.push(DiffEntry::Same { left, right });
}
pub fn same(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
self.entries
.iter()
.filter(|entry| matches!(entry, DiffEntry::Same { .. }))
}
pub fn all_changes(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
self.entries.iter()
}
pub fn replaced(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
self.entries.iter().filter(|entry| entry.is_replaced())
}
pub fn added(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
self.entries.iter().filter(|entry| entry.is_added())
}
pub fn removed(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
self.entries.iter().filter(|entry| entry.is_removed())
}
pub fn entries(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
self.entries.iter()
}
}
#[derive(Debug, Clone, Copy)]
pub enum DiffEntry {
Replaced {
left: SymbolId,
right: SymbolId,
detail: ReplaceDetail,
},
Same {
left: SymbolId,
right: SymbolId,
},
Added {
right: SymbolId,
},
Removed {
left: SymbolId,
},
}
impl DiffEntry {
fn is_added(&self) -> bool {
matches!(self, DiffEntry::Added { .. })
}
fn is_removed(&self) -> bool {
matches!(self, DiffEntry::Removed { .. })
}
fn is_replaced(&self) -> bool {
matches!(self, DiffEntry::Replaced { .. })
}
}
#[derive(Debug, Clone, Copy)]
pub enum ReplaceDetail {
BodyChanged,
ChildrenChanged,
UnresolvedChildren(SymbolId),
}
#[derive(Ord, PartialOrd, PartialEq, Eq)]
struct SymbolKey<'a> {
stable_name: Option<&'a str>,
}
#[derive(Default, Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
struct SymbolContext {
parents: SVec<SymbolId>,
}
impl SymbolContext {
fn num_same_parents(&self, other: &SymbolContext) -> usize {
let mut same = 0;
for parent in &self.parents {
if other.parents.contains(parent) {
same += 1;
}
}
same
}
}
#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
struct SymbolWithContext {
pub symbol: SymbolId,
pub context: SymbolContext,
}
impl SymbolMapping {
fn match_list_by_context(
&mut self,
old_contexts: &mut SVec<SymbolWithContext>,
new_contexts: &mut SVec<SymbolWithContext>,
) {
let len_before = old_contexts.len() + new_contexts.len() + self.left_to_right.len() * 2;
self.match_by_exact_parents(old_contexts, new_contexts);
let len_after = old_contexts.len() + new_contexts.len() + self.left_to_right.len() * 2;
debug_assert_eq!(len_before, len_after);
self.match_by_changed_parents(old_contexts, new_contexts);
let len_after = old_contexts.len() + new_contexts.len() + self.left_to_right.len() * 2;
debug_assert_eq!(len_before, len_after);
}
fn match_by_exact_parents(
&mut self,
old_contexts: &mut SVec<SymbolWithContext>,
new_contexts: &mut SVec<SymbolWithContext>,
) {
let old_iter = mem::take(old_contexts);
let mut new_vec = mem::take(new_contexts);
for old_ctx in old_iter {
let with_same_context = new_vec
.iter()
.enumerate()
.find(|(_, new_ctx)| &old_ctx.context == &new_ctx.context);
let Some((id, _)) = with_same_context else {
old_contexts.push(old_ctx);
continue;
};
let new_ctx = new_vec.remove(id);
self.left_to_right.insert(old_ctx.symbol, new_ctx.symbol);
}
*new_contexts = new_vec;
}
fn match_by_changed_parents(
&mut self,
old_contexts: &mut SVec<SymbolWithContext>,
new_contexts: &mut SVec<SymbolWithContext>,
) {
let old_iter = mem::take(old_contexts);
let mut new_vec = mem::take(new_contexts)
.into_iter()
.enumerate()
.collect::<Vec<_>>();
for old_ctx in old_iter {
new_vec.sort_by_key(|(_, b)| b.context.num_same_parents(&old_ctx.context));
let new_ctx = match new_vec.as_slice() {
&[.., (_, ref prev), _] if prev.context.num_same_parents(&old_ctx.context) > 0 => {
old_contexts.push(old_ctx);
continue;
}
&[] => {
old_contexts.push(old_ctx);
continue;
}
&[(_, ref new_ctx)] if new_ctx.context.num_same_parents(&old_ctx.context) == 0 => {
old_contexts.push(old_ctx);
continue;
}
&[..] => new_vec.pop().unwrap().1,
};
log::warn!(
"Matched symbol by changed context: old {:?}, new {:?}",
old_ctx,
new_ctx
);
self.left_to_right.insert(old_ctx.symbol, new_ctx.symbol);
}
new_vec.sort_by_key(|(original_order, _)| *original_order);
*new_contexts = new_vec.into_iter().map(|(_, ctx)| ctx).collect();
}
}
pub trait SymbolMapWithContent<'src> {
fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>>;
fn symbols(&self) -> &SymbolMap<'src>;
}
impl<'src> SymbolMapWithContent<'src> for analysis::ModuleInfo<'src> {
fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>> {
self.symbols
.get(sym_id)
.and_then(|s| s.stable_content(self))
}
fn symbols(&self) -> &SymbolMap<'src> {
&self.symbols
}
}
#[derive(Clone, Default, Debug)]
pub struct StaticModuleInfo {
symbols: SymbolMap<'static>,
contents: IdMap<SymbolId, Vec<u8>>,
}
impl StaticModuleInfo {
pub fn empty() -> Self {
Self {
symbols: SymbolMap::empty(),
contents: IdMap::new(),
}
}
pub fn new(info: &analysis::ModuleInfo<'_>) -> Self {
let symbols = info.symbols.clone_owned();
let mut contents = IdMap::new();
for (sym_id, symbol) in info.symbols.iter() {
if let Some(content) = symbol.stable_content(info) {
contents.insert(sym_id, content);
}
}
Self { symbols, contents }
}
}
impl SymbolMapWithContent<'static> for StaticModuleInfo {
fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>> {
self.contents.get(sym_id).cloned()
}
fn symbols(&self) -> &SymbolMap<'static> {
&self.symbols
}
}
impl<'a, 'src, M> SymbolMapWithContent<'src> for &'a M
where
M: SymbolMapWithContent<'src>,
{
fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>> {
<M as SymbolMapWithContent<'src>>::stable_content(*self, sym_id)
}
fn symbols(&self) -> &SymbolMap<'src> {
<M as SymbolMapWithContent<'src>>::symbols(*self)
}
}