Skip to main content

wamex_cli/analysis/symbols/
diff.rs

1//!
2//! Diff implemented in two phases:
3//! 1) Mapping symbols from left to right module by their names. (Remaining symbols are treated as added or removed)
4//! 2) Calculate canonical hash + (child_ids) of function bodies for mapped functions and compare them to detect modified functions.
5//!
6//! Note: .L symbols is not guarateed to persist between compilations. So to detect this symbols we may use "contexts" (parent symbols).
7
8use std::{collections::BTreeMap, mem, ops::Deref};
9
10use crate::{
11    analysis::{self, SymbolMap},
12    index::{IdMap, SymbolId},
13};
14
15pub struct SymbolMapping {
16    // Most of symbols are mapped.
17    left_to_right: IdMap<SymbolId, SymbolId>,
18    left_non_matched: Vec<SymbolId>,
19    right_non_matched: Vec<SymbolId>,
20}
21
22impl SymbolMapping {
23    pub fn mapped_list(&self) -> impl Iterator<Item = (SymbolId, SymbolId)> + use<'_> {
24        self.left_to_right
25            .iter()
26            .map(|(right, left)| (right, *left))
27    }
28
29    pub fn map(&self, left: SymbolId) -> Option<SymbolId> {
30        self.left_to_right.get(left).copied()
31    }
32    pub fn left_only(&self) -> impl Iterator<Item = SymbolId> + use<'_> {
33        self.left_non_matched.iter().copied()
34    }
35    pub fn right_only(&self) -> impl Iterator<Item = SymbolId> + use<'_> {
36        self.right_non_matched.iter().copied()
37    }
38}
39
40type SVec<T, const SIZE: usize = 4> = smallvec::SmallVec<[T; SIZE]>;
41
42pub struct Differ<L, R> {
43    left: L,
44    right: R,
45}
46
47impl<'left, 'right, L, R> Differ<L, R>
48where
49    L: SymbolMapWithContent<'left>,
50    R: SymbolMapWithContent<'right>,
51{
52    pub fn new(left: L, right: R) -> Self {
53        Self { left, right }
54    }
55    pub fn symbol_map(&self) -> SymbolMapping {
56        let mut mapping = self.build_name_mapping();
57        self.try_match_wamex_split_point(&mut mapping);
58        self.refine_mapping(&mut mapping);
59        mapping
60    }
61
62    fn is_anon_name(name: &str) -> bool {
63        name.starts_with(".L") || name.starts_with("$L")
64    }
65
66    fn build_name_mapping(&self) -> SymbolMapping {
67        //1. Build name -> symbol id maps for first_module;
68        // If more than one symbol with same name found - we treat them as duplicates and compare by context later.
69        let mut name_to_left_symbol: BTreeMap<&str, SymbolId> = BTreeMap::new();
70        let mut duplicate_left: BTreeMap<&str, SVec<SymbolId>> = BTreeMap::new();
71        for (sym_id, symbol) in self.left.symbols().iter() {
72            if let Some(name) = &symbol.linking_name {
73                if Self::is_anon_name(name) {
74                    continue;
75                }
76                if let Some(prev) = name_to_left_symbol.insert(name, sym_id) {
77                    duplicate_left.entry(name).or_default().push(prev);
78                }
79            }
80        }
81
82        //1.2. Duplicate are handled separately - remove them from main map.
83        let duplicate_names: SVec<_, 16> = duplicate_left.keys().cloned().collect();
84        for name in duplicate_names {
85            let last = name_to_left_symbol.remove(name).unwrap();
86            let duplicates = duplicate_left.get_mut(name).unwrap();
87            duplicates.push(last);
88        }
89
90        //2. Match left symbols to the right.
91        let mut mapping = IdMap::new();
92
93        let mut non_matched_right_symbols: Vec<SymbolId> = Vec::new();
94        let mut dups: SVec<_, 16> = SVec::new();
95        for (right_sym_id, right_symbol) in self.right.symbols().iter() {
96            if let Some(name) = &right_symbol.linking_name
97                && !Self::is_anon_name(name)
98            {
99                if let Some(&left_sym_id) = name_to_left_symbol.get(name.deref()) {
100                    if let Some(dup) = mapping.insert(left_sym_id, right_sym_id) {
101                        dups.push(left_sym_id);
102                        non_matched_right_symbols.push(dup);
103                    }
104                    continue;
105                }
106            }
107            non_matched_right_symbols.push(right_sym_id);
108        }
109
110        //2.2 handle duplicates
111        for dup in dups {
112            let right = mapping.remove(dup).unwrap();
113            non_matched_right_symbols.push(right);
114        }
115
116        // TODO: optimize by itering over mapping keys.
117        let non_matched_left_symbols: Vec<SymbolId> = self
118            .left
119            .symbols()
120            .iter()
121            .filter_map(|(left_sym_id, _)| {
122                if mapping.get(left_sym_id).is_none() {
123                    Some(left_sym_id)
124                } else {
125                    None
126                }
127            })
128            .collect();
129
130        SymbolMapping {
131            left_to_right: mapping,
132            left_non_matched: non_matched_left_symbols,
133            right_non_matched: non_matched_right_symbols,
134        }
135    }
136
137    fn wamex_parse_name(name: &str) -> Option<(&str, &str)> {
138        use analysis::split_point::{
139            SPLIT_EXPORT_POSTFIX, SPLIT_IMPORT_POSTFIX, WAMEX_ENTRY_PREFIX, parser,
140        };
141        if let Some(v) = parser(name, WAMEX_ENTRY_PREFIX, SPLIT_IMPORT_POSTFIX) {
142            return Some(v);
143        };
144        parser(name, WAMEX_ENTRY_PREFIX, SPLIT_EXPORT_POSTFIX)
145    }
146    // Try to match unmatched wamex split points by their module name and function position.
147    fn try_match_wamex_split_point(&self, mapping: &mut SymbolMapping) {
148        // To avoid conflicts wamex entrypoints contain unique portion in their names.
149        // So we match them by module name.
150        let mut non_matched_wamex_left_symbols: BTreeMap<&str, SVec<(&str, SymbolId)>> =
151            BTreeMap::new();
152        let mut non_matched_wamex_right_symbols: BTreeMap<&str, SVec<(&str, SymbolId)>> =
153            BTreeMap::new();
154        for left in &mapping.left_non_matched {
155            let left_symbol = &self.left.symbols().get(*left).unwrap();
156            let Some(name) = &left_symbol.linking_name else {
157                continue;
158            };
159            if !name.contains(analysis::split_point::WAMEX_ENTRY_PREFIX) {
160                continue;
161            }
162            let Some((module, fn_name)) = Self::wamex_parse_name(name) else {
163                continue;
164            };
165            non_matched_wamex_left_symbols
166                .entry(module)
167                .or_default()
168                .push((fn_name, *left));
169        }
170        for right in &mapping.right_non_matched {
171            let right_symbol = &self.right.symbols().get(*right).unwrap();
172            let Some(name) = &right_symbol.linking_name else {
173                continue;
174            };
175            if !name.contains(analysis::split_point::WAMEX_ENTRY_PREFIX) {
176                continue;
177            }
178            let Some((module, fn_name)) = Self::wamex_parse_name(name) else {
179                continue;
180            };
181            non_matched_wamex_right_symbols
182                .entry(module)
183                .or_default()
184                .push((fn_name, *right));
185        }
186        // Now match left and right symbols within same module by function number
187        for (module, mut left_syms) in non_matched_wamex_left_symbols {
188            let Some(mut right_syms) = non_matched_wamex_right_symbols.remove(module) else {
189                continue;
190            };
191            left_syms.sort_by_key(|(fn_name, _)| *fn_name);
192            right_syms.sort_by_key(|(fn_name, _)| *fn_name);
193            for (left, right) in left_syms.into_iter().zip(right_syms.into_iter()) {
194                log::info!(
195                    "Matched wamex split point symbol: module: {module}, left: {:?}, right: {:?}",
196                    left.0,
197                    right.0
198                );
199                mapping.left_to_right.insert(left.1, right.1);
200                // Remove from non-matched lists
201                mapping.left_non_matched.retain(|v| *v != left.1);
202                mapping.right_non_matched.retain(|v| *v != right.1);
203            }
204        }
205    }
206
207    // Calculate hash and check if hashes and childs are same
208    fn is_same_content(
209        &self,
210        mapping: &SymbolMapping,
211        left_sym_id: SymbolId,
212        right_sym_id: SymbolId,
213    ) -> Result<(), ReplaceDetail> {
214        let left_symbol = &self.left.symbols().get(left_sym_id).unwrap();
215        let right_symbol = &self.right.symbols().get(right_sym_id).unwrap();
216
217        let left_content = self.left.stable_content(left_sym_id);
218        let right_content = self.right.stable_content(right_sym_id);
219        if left_content != right_content {
220            return Err(ReplaceDetail::BodyChanged);
221        }
222
223        // 2. check childs
224        let right_childs = right_symbol.childs().collect::<Vec<_>>();
225
226        // List of right childs mapped to left symbols
227        let mut left_childs: Vec<SymbolId> = Vec::new();
228        for left_sym in left_symbol.childs() {
229            let Some(&right_sym_mapped) = mapping.left_to_right.get(left_sym) else {
230                return Err(ReplaceDetail::UnresolvedChildren(left_sym));
231            };
232            left_childs.push(right_sym_mapped);
233        }
234
235        if left_childs.len() != right_childs.len() {
236            return Err(ReplaceDetail::ChildrenChanged);
237        }
238
239        Ok(())
240    }
241
242    // Try to match non-mapped symbols.
243    // If symbols have not changed, matching can still fail if name is not unique, or not changed.
244    // To deal with this, we can add more pieces of information to the matching process:
245    // 1. Use stable name (don't use .L names that can change between compilations)
246    // 2. Use content and list of childs to match symbols that have not changed.
247    // 3. If not working - use context (parent symbols) to find where symbol was used.
248    fn refine_mapping(&self, mapping: &mut SymbolMapping) {
249        // 1. Build parent map for left symbols
250        let mut left_parent_map: BTreeMap<SymbolId, SymbolContext> = BTreeMap::new();
251        for (sym_id, symbol) in self.left.symbols().iter() {
252            for child in symbol.childs() {
253                left_parent_map
254                    .entry(child)
255                    .or_default()
256                    .parents
257                    .push(sym_id);
258            }
259        }
260
261        // 2. Build parent map for right symbols
262        let mut right_parent_map: BTreeMap<SymbolId, SymbolContext> = BTreeMap::new();
263        for (sym_id, symbol) in self.right.symbols().iter() {
264            for child in symbol.childs() {
265                right_parent_map
266                    .entry(child)
267                    .or_default()
268                    .parents
269                    .push(sym_id);
270            }
271        }
272
273        // TODO: because candidate key is only stable name, anonymous symbols are placed under same key (None).
274        // Algorithm can be improved:
275        // 1. functions can be handled separately so "candidate key" can be moved outside of this function.
276        // 2. Build depgraph of not-matched symbols (left or right??) and iterate only when some of parents are resolved.
277        // 3. What to do with recursive symbols?
278
279        // 3. Build candidates
280        let mut right_candidates = BTreeMap::<_, SVec<_>>::new();
281        for right_sym in std::mem::take(&mut mapping.right_non_matched) {
282            let right_symbol = &self.right.symbols().get(right_sym).unwrap();
283            let context = right_parent_map
284                .get(&right_sym)
285                .cloned()
286                .unwrap_or_default();
287
288            // This will put all anonymous symbols under same key.
289            // TODO: Consider adding content hash to the key to reduce number of candidates.
290
291            let key = SymbolKey {
292                stable_name: right_symbol.stable_name(),
293            };
294
295            let entry = right_candidates.entry(key).or_default();
296            entry.push(SymbolWithContext {
297                symbol: right_sym,
298                context,
299            });
300        }
301        //3.1. For left candidates not all parents/childs can be mapped so process is iterative.
302
303        let mut queue = std::mem::take(&mut mapping.left_non_matched);
304
305        // Currently this loop is process every not processed symbol each iteration.
306        // But we can optimize it, by building dependent graph, and process only when some of parents are resolved.
307        loop {
308            let queue_len = queue.len();
309            // Build left candidates
310            let mut left_candidates = BTreeMap::<_, SVec<_>>::new();
311            for left_sym in std::mem::take(&mut queue) {
312                // Try match candidate.
313                let left_mapped_candidate_key = {
314                    let left_symbol = &self.left.symbols().get(left_sym).unwrap();
315
316                    SymbolKey {
317                        stable_name: left_symbol.stable_name(),
318                    }
319                };
320
321                let mut context = left_parent_map.get(&left_sym).cloned().unwrap_or_default();
322                let parents: Option<SVec<_>> = std::mem::take(&mut context.parents)
323                    .into_iter()
324                    .map(|parent| mapping.left_to_right.get(parent).copied())
325                    .collect();
326                let Some(parents) = parents else {
327                    // Some parents are not mapped yet - skip for now.
328                    queue.push(left_sym);
329                    continue;
330                };
331                context.parents = parents;
332
333                left_candidates
334                    .entry(left_mapped_candidate_key)
335                    .or_default()
336                    .push(SymbolWithContext {
337                        symbol: left_sym,
338                        context,
339                    });
340            }
341            for (key, mut left_syms) in left_candidates {
342                let Some(mut right_syms) = right_candidates.remove(&key) else {
343                    // No candidates on right side - push all left symbols back to removed list.
344
345                    for s in left_syms {
346                        mapping.left_non_matched.push(s.symbol);
347                    }
348                    continue;
349                };
350                mapping.match_list_by_context(&mut left_syms, &mut right_syms);
351                // Return unmatched right symbols back to the map.
352                // and left back to the queue.
353                if !right_syms.is_empty() {
354                    assert!(right_candidates.insert(key, right_syms).is_none());
355                }
356                for s in left_syms {
357                    queue.push(s.symbol);
358                }
359            }
360
361            if queue.is_empty() || queue.len() == queue_len {
362                break;
363            }
364        }
365
366        // 4. Return unmatched symbols back to the mapping.
367        for (_, right_syms) in right_candidates {
368            for s in right_syms {
369                mapping.right_non_matched.push(s.symbol);
370            }
371        }
372
373        for left_sym in queue {
374            mapping.left_non_matched.push(left_sym);
375        }
376    }
377
378    pub fn build_diff(&self, mapping: &SymbolMapping) -> DiffResult {
379        let mut diff_result = DiffResult::new();
380
381        // Process mapped symbols
382        for (left_sym_id, right_sym_id) in mapping.mapped_list() {
383            match self.is_same_content(mapping, left_sym_id, right_sym_id) {
384                Ok(()) => diff_result.push_same(left_sym_id, right_sym_id),
385                Err(detail) => diff_result.push_replaced(left_sym_id, right_sym_id, detail),
386            }
387        }
388
389        // Process non-matched left symbols (removed)
390        for left_sym_id in mapping.left_only() {
391            diff_result.push_removed(left_sym_id);
392        }
393
394        // Process non-matched right symbols (added)
395        for right_sym_id in mapping.right_only() {
396            diff_result.push_added(right_sym_id);
397        }
398
399        diff_result
400    }
401
402    fn left_sym_name<'a>(&'a self, sym_id: SymbolId) -> Option<&'a str>
403    where
404        'left: 'a,
405    {
406        self.left.symbols().get(sym_id).map(|s| &*s.name)
407    }
408    fn right_sym_name<'a>(&'a self, sym_id: SymbolId) -> Option<&'a str>
409    where
410        'right: 'a,
411    {
412        self.right.symbols().get(sym_id).map(|s| &*s.name)
413    }
414    pub fn debug_diff(&self, diff: &DiffResult) {
415        let replaced_iter = diff.replaced();
416        let added_iter = diff.added();
417        let removed_iter = diff.removed();
418        // Debug output
419        println!(
420            "Replaced: {}, Added: {}, Removed: {}, Same: {}",
421            replaced_iter.clone().count(),
422            added_iter.clone().count(),
423            removed_iter.clone().count(),
424            diff.same().count()
425        );
426
427        for entry in added_iter {
428            let DiffEntry::Added { right } = entry else {
429                continue;
430            };
431            let name = self.right_sym_name(*right).unwrap_or("<unknown>");
432            println!("Added: {name} [{index}]", index = right);
433        }
434        for entry in removed_iter {
435            let DiffEntry::Removed { left } = entry else {
436                continue;
437            };
438            let name = self.left_sym_name(*left).unwrap_or("<unknown>");
439            println!("Removed: {name} [{index}]", index = left);
440        }
441        for entry in replaced_iter {
442            let DiffEntry::Replaced {
443                left,
444                right,
445                detail,
446            } = entry
447            else {
448                continue;
449            };
450            let left_name = self.left_sym_name(*left).unwrap_or("<unknown>");
451            let right_name = self.right_sym_name(*right).unwrap_or("<unknown>");
452
453            let detail = match detail {
454                ReplaceDetail::BodyChanged => {
455                    let left_content = self.left.stable_content(*left).unwrap_or_default();
456                    let right_content = self.right.stable_content(*right).unwrap_or_default();
457                    format_args!(
458                        "Body changed from {left_content} to {right_content}",
459                        left_content = hex::encode(left_content),
460                        right_content = hex::encode(right_content)
461                    )
462                }
463                ReplaceDetail::ChildrenChanged => {
464                    // TODO: add list of deps that were changed.
465                    format_args!("Children changed")
466                }
467                ReplaceDetail::UnresolvedChildren(v) => {
468                    format_args!("Unresolved child symbol id: {v}", v = *v)
469                }
470            };
471            println!(
472                "Replaced: {left_name} [{left_index}] -> {right_name} [{right_index}] Detail: {detail}",
473                left_index = left,
474                right_index = right
475            );
476        }
477    }
478}
479
480/// Result of diffing two structures
481#[derive(Debug, Clone)]
482pub struct DiffResult {
483    entries: Vec<DiffEntry>,
484}
485
486impl FromIterator<DiffEntry> for DiffResult {
487    fn from_iter<T: IntoIterator<Item = DiffEntry>>(iter: T) -> Self {
488        let mut diff_result = DiffResult::new();
489        for entry in iter {
490            diff_result.push(entry);
491        }
492        diff_result
493    }
494}
495
496impl DiffResult {
497    pub fn new() -> Self {
498        Self {
499            entries: Vec::new(),
500        }
501    }
502    pub fn is_empty(&self) -> bool {
503        self.entries.is_empty()
504    }
505    pub fn push(&mut self, entry: DiffEntry) {
506        self.entries.push(entry);
507    }
508    pub fn push_added(&mut self, right: SymbolId) {
509        self.entries.push(DiffEntry::Added { right });
510    }
511    pub fn push_removed(&mut self, left: SymbolId) {
512        self.entries.push(DiffEntry::Removed { left });
513    }
514    pub fn push_replaced(&mut self, left: SymbolId, right: SymbolId, detail: ReplaceDetail) {
515        self.entries.push(DiffEntry::Replaced {
516            left,
517            right,
518            detail,
519        });
520    }
521    pub fn push_same(&mut self, left: SymbolId, right: SymbolId) {
522        self.entries.push(DiffEntry::Same { left, right });
523    }
524
525    /// List of nodes that remain same.
526    /// This nodes should have same signature, body hash and childs but may have different parent nodes.
527    pub fn same(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
528        self.entries
529            .iter()
530            .filter(|entry| matches!(entry, DiffEntry::Same { .. }))
531    }
532    /// List of nodes that cannot be matched in old and new structures.
533    /// It will contain full list of removed, added or replaced nodes.
534    ///
535    /// This can be false positive, if signature or content hash was changed.
536    pub fn all_changes(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
537        self.entries.iter()
538    }
539    /// List of nodes that was changed, but filter only those that was matched in old and new structures.
540    pub fn replaced(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
541        self.entries.iter().filter(|entry| entry.is_replaced())
542    }
543    /// List nodes that was added in new structure.
544    pub fn added(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
545        self.entries.iter().filter(|entry| entry.is_added())
546    }
547    /// List nodes that was removed in new structure.
548    pub fn removed(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
549        self.entries.iter().filter(|entry| entry.is_removed())
550    }
551
552    pub fn entries(&self) -> impl Iterator<Item = &DiffEntry> + Clone {
553        self.entries.iter()
554    }
555}
556
557#[derive(Debug, Clone, Copy)]
558pub enum DiffEntry {
559    Replaced {
560        left: SymbolId,
561        right: SymbolId,
562        detail: ReplaceDetail,
563    },
564    Same {
565        left: SymbolId,
566        right: SymbolId,
567    },
568    Added {
569        right: SymbolId,
570    },
571    Removed {
572        left: SymbolId,
573    },
574}
575impl DiffEntry {
576    fn is_added(&self) -> bool {
577        matches!(self, DiffEntry::Added { .. })
578    }
579    fn is_removed(&self) -> bool {
580        matches!(self, DiffEntry::Removed { .. })
581    }
582    fn is_replaced(&self) -> bool {
583        matches!(self, DiffEntry::Replaced { .. })
584    }
585}
586
587#[derive(Debug, Clone, Copy)]
588pub enum ReplaceDetail {
589    /// Content is not equal
590    BodyChanged,
591    /// Relocations symbols are different
592    ChildrenChanged,
593    /// Some of children cannot be mapped,
594    /// and we cannot be sure if they are same or not.
595    UnresolvedChildren(SymbolId),
596}
597
598// Fuzzy matching based on symbol content and context.
599
600#[derive(Ord, PartialOrd, PartialEq, Eq)]
601struct SymbolKey<'a> {
602    stable_name: Option<&'a str>,
603    // childs: SVec<SymbolId>,
604    // content: Option<Vec<u8>>,
605}
606
607#[derive(Default, Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
608struct SymbolContext {
609    parents: SVec<SymbolId>,
610}
611
612impl SymbolContext {
613    fn num_same_parents(&self, other: &SymbolContext) -> usize {
614        let mut same = 0;
615        for parent in &self.parents {
616            if other.parents.contains(parent) {
617                same += 1;
618            }
619        }
620        same
621    }
622}
623
624#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)]
625struct SymbolWithContext {
626    pub symbol: SymbolId,
627    pub context: SymbolContext,
628}
629
630impl SymbolMapping {
631    fn match_list_by_context(
632        &mut self,
633        old_contexts: &mut SVec<SymbolWithContext>,
634        new_contexts: &mut SVec<SymbolWithContext>,
635    ) {
636        let len_before = old_contexts.len() + new_contexts.len() + self.left_to_right.len() * 2;
637
638        // Priority 1: Exact parent context match
639        self.match_by_exact_parents(old_contexts, new_contexts);
640
641        let len_after = old_contexts.len() + new_contexts.len() + self.left_to_right.len() * 2;
642        debug_assert_eq!(len_before, len_after);
643
644        // // Priority 2: Matching with partial parents similarity (added/removed parent)
645        self.match_by_changed_parents(old_contexts, new_contexts);
646
647        let len_after = old_contexts.len() + new_contexts.len() + self.left_to_right.len() * 2;
648        debug_assert_eq!(len_before, len_after);
649    }
650
651    /// Match by exact parent contexts (same parents)
652    fn match_by_exact_parents(
653        &mut self,
654        old_contexts: &mut SVec<SymbolWithContext>,
655        new_contexts: &mut SVec<SymbolWithContext>,
656    ) {
657        let old_iter = mem::take(old_contexts);
658
659        let mut new_vec = mem::take(new_contexts);
660
661        for old_ctx in old_iter {
662            let with_same_context = new_vec
663                .iter()
664                .enumerate()
665                .find(|(_, new_ctx)| &old_ctx.context == &new_ctx.context);
666
667            let Some((id, _)) = with_same_context else {
668                old_contexts.push(old_ctx);
669                continue;
670            };
671            let new_ctx = new_vec.remove(id);
672            self.left_to_right.insert(old_ctx.symbol, new_ctx.symbol);
673        }
674
675        *new_contexts = new_vec;
676    }
677
678    // Compare nodes with parents partially equal.
679    fn match_by_changed_parents(
680        &mut self,
681        old_contexts: &mut SVec<SymbolWithContext>,
682        new_contexts: &mut SVec<SymbolWithContext>,
683    ) {
684        let old_iter = mem::take(old_contexts);
685
686        let mut new_vec = mem::take(new_contexts)
687            .into_iter()
688            .enumerate()
689            .collect::<Vec<_>>();
690
691        for old_ctx in old_iter {
692            new_vec.sort_by_key(|(_, b)| b.context.num_same_parents(&old_ctx.context));
693
694            // If more than one candidate context is found, then we cannot uniquely match
695            let new_ctx = match new_vec.as_slice() {
696                // More than one candidate with same similarity
697                // or no similarity at all.
698                &[.., (_, ref prev), _] if prev.context.num_same_parents(&old_ctx.context) > 0 => {
699                    old_contexts.push(old_ctx);
700                    continue;
701                }
702                // No candidates
703                &[] => {
704                    old_contexts.push(old_ctx);
705                    continue;
706                }
707                // no similarity
708                &[(_, ref new_ctx)] if new_ctx.context.num_same_parents(&old_ctx.context) == 0 => {
709                    old_contexts.push(old_ctx);
710                    continue;
711                }
712                &[..] => new_vec.pop().unwrap().1,
713            };
714
715            // Symbol is same by key and partially by context.
716            log::warn!(
717                "Matched symbol by changed context: old {:?}, new {:?}",
718                old_ctx,
719                new_ctx
720            );
721
722            self.left_to_right.insert(old_ctx.symbol, new_ctx.symbol);
723        }
724        new_vec.sort_by_key(|(original_order, _)| *original_order);
725        *new_contexts = new_vec.into_iter().map(|(_, ctx)| ctx).collect();
726    }
727}
728
729pub trait SymbolMapWithContent<'src> {
730    fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>>;
731    fn symbols(&self) -> &SymbolMap<'src>;
732}
733
734impl<'src> SymbolMapWithContent<'src> for analysis::ModuleInfo<'src> {
735    fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>> {
736        self.symbols
737            .get(sym_id)
738            .and_then(|s| s.stable_content(self))
739    }
740    fn symbols(&self) -> &SymbolMap<'src> {
741        &self.symbols
742    }
743}
744
745#[derive(Clone, Default, Debug)]
746pub struct StaticModuleInfo {
747    symbols: SymbolMap<'static>,
748    contents: IdMap<SymbolId, Vec<u8>>,
749}
750
751impl StaticModuleInfo {
752    pub fn empty() -> Self {
753        Self {
754            symbols: SymbolMap::empty(),
755            contents: IdMap::new(),
756        }
757    }
758    pub fn new(info: &analysis::ModuleInfo<'_>) -> Self {
759        let symbols = info.symbols.clone_owned();
760        let mut contents = IdMap::new();
761        for (sym_id, symbol) in info.symbols.iter() {
762            if let Some(content) = symbol.stable_content(info) {
763                contents.insert(sym_id, content);
764            }
765        }
766        Self { symbols, contents }
767    }
768}
769
770impl SymbolMapWithContent<'static> for StaticModuleInfo {
771    //TODO: avoid clone
772    fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>> {
773        self.contents.get(sym_id).cloned()
774    }
775    fn symbols(&self) -> &SymbolMap<'static> {
776        &self.symbols
777    }
778}
779
780impl<'a, 'src, M> SymbolMapWithContent<'src> for &'a M
781where
782    M: SymbolMapWithContent<'src>,
783{
784    fn stable_content(&self, sym_id: SymbolId) -> Option<Vec<u8>> {
785        <M as SymbolMapWithContent<'src>>::stable_content(*self, sym_id)
786    }
787    fn symbols(&self) -> &SymbolMap<'src> {
788        <M as SymbolMapWithContent<'src>>::symbols(*self)
789    }
790}