| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 """Logic for diffing two SizeInfo objects.""" |
| 5 |
| 6 import collections |
| 7 |
| 8 import models |
| 9 |
| 10 |
| 11 def _CloneSymbol(sym, size): |
| 12 """Returns a copy of |sym| with an updated |size|. |
| 13 |
| 14 Padding and aliases are not copied. |
| 15 """ |
| 16 return models.Symbol( |
| 17 sym.section_name, size, address=sym.address, name=sym.name, |
| 18 source_path=sym.source_path, object_path=sym.object_path, |
| 19 full_name=sym.full_name, flags=sym.flags) |
| 20 |
| 21 |
| 22 def _CloneAlias(sym, diffed_alias): |
| 23 """Returns a copy of |sym| and making it an alias of |diffed_alias|.""" |
| 24 ret = _CloneSymbol(sym, diffed_alias.size_without_padding) |
| 25 ret.padding = diffed_alias.padding |
| 26 ret.aliases = diffed_alias.aliases |
| 27 ret.aliases.append(ret) |
| 28 return ret |
| 29 |
| 30 |
| 31 def _DiffSymbol(before_sym, after_sym, diffed_symbol_by_after_aliases, |
| 32 padding_by_section_name): |
| 33 diffed_alias = None |
| 34 if after_sym.aliases: |
| 35 diffed_alias = diffed_symbol_by_after_aliases.get(id(after_sym.aliases)) |
| 36 |
| 37 if diffed_alias: |
| 38 ret = _CloneAlias(after_sym, diffed_alias) |
| 39 else: |
| 40 size_diff = (after_sym.size_without_padding - |
| 41 before_sym.size_without_padding) |
| 42 ret = _CloneSymbol(after_sym, size_diff) |
| 43 # Diffs are more stable when comparing size without padding, except when |
| 44 # the symbol is a padding-only symbol. |
| 45 if after_sym.size_without_padding == 0 and size_diff == 0: |
| 46 ret.padding = after_sym.padding - before_sym.padding |
| 47 else: |
| 48 padding_diff = after_sym.padding - before_sym.padding |
| 49 padding_by_section_name[ret.section_name] += padding_diff |
| 50 |
| 51 if after_sym.aliases: |
| 52 ret.aliases = [ret] |
| 53 diffed_symbol_by_after_aliases[id(after_sym.aliases)] = ret |
| 54 return ret |
| 55 |
| 56 |
| 57 def _CloneUnmatched(after_symbols, diffed_symbol_by_after_aliases): |
| 58 ret = [] |
| 59 for sym in after_symbols: |
| 60 cloned = sym |
| 61 if sym.IsGroup(): |
| 62 cloned = models.SymbolDiff( |
| 63 [], _CloneUnmatched(sym, diffed_symbol_by_after_aliases), [], |
| 64 name=sym.name, full_name=sym.full_name, section_name=sym.section_name) |
| 65 elif sym.aliases: |
| 66 diffed_alias = diffed_symbol_by_after_aliases.get(id(sym.aliases)) |
| 67 if diffed_alias: |
| 68 # At least one alias was diffed. |
| 69 cloned = _CloneAlias(sym, diffed_alias) |
| 70 ret.append(cloned) |
| 71 return ret |
| 72 |
| 73 |
| 74 def _NegateAndClone(before_symbols, matched_before_aliases, |
| 75 negated_symbol_by_before_aliases): |
| 76 ret = [] |
| 77 for sym in before_symbols: |
| 78 if sym.IsGroup(): |
| 79 cloned = models.SymbolDiff( |
| 80 [], _NegateAndClone(sym, matched_before_aliases, |
| 81 negated_symbol_by_before_aliases), [], |
| 82 name=sym.name, full_name=sym.full_name, section_name=sym.section_name) |
| 83 else: |
| 84 negated_alias = None |
| 85 if sym.aliases: |
| 86 negated_alias = negated_symbol_by_before_aliases.get(id(sym.aliases)) |
| 87 if negated_alias: |
| 88 cloned = _CloneAlias(sym, negated_alias) |
| 89 else: |
| 90 all_aliases_removed = id(sym.aliases) not in matched_before_aliases |
| 91 if all_aliases_removed: |
| 92 cloned = _CloneSymbol(sym, -sym.size_without_padding) |
| 93 cloned.padding = -sym.padding |
| 94 else: |
| 95 cloned = _CloneSymbol(sym, 0) |
| 96 cloned.aliases = [cloned] |
| 97 negated_symbol_by_before_aliases[id(sym.aliases)] = cloned |
| 98 else: |
| 99 cloned = _CloneSymbol(sym, -sym.size_without_padding) |
| 100 cloned.padding = -sym.padding |
| 101 ret.append(cloned) |
| 102 return ret |
| 103 |
| 104 |
| 105 # TODO(agrieve): I doubt this works correctly for aliases that include [clone] |
| 106 # symbols that have been grouped into nested SymbolGroups. I'm guessing this |
| 107 # won't be a big issue if it's not correct though (likely won't come up, and |
| 108 # if it does will account for small size). |
| 109 def _DiffSymbolGroups(before, after): |
| 110 before_symbols_by_key = collections.defaultdict(list) |
| 111 for s in before: |
| 112 before_symbols_by_key[s._Key()].append(s) |
| 113 |
| 114 similar = [] |
| 115 diffed_symbol_by_after_aliases = {} |
| 116 matched_before_aliases = set() |
| 117 unmatched_after_syms = [] |
| 118 # For similar symbols, padding is zeroed out. In order to not lose the |
| 119 # information entirely, store it in aggregate. |
| 120 padding_by_section_name = collections.defaultdict(int) |
| 121 |
| 122 # Step 1: Create all delta symbols and record unmatched symbols. |
| 123 for after_sym in after: |
| 124 matching_syms = before_symbols_by_key.get(after_sym._Key()) |
| 125 if matching_syms: |
| 126 before_sym = matching_syms.pop(0) |
| 127 if before_sym.IsGroup() and after_sym.IsGroup(): |
| 128 similar.append(_DiffSymbolGroups(before_sym, after_sym)) |
| 129 else: |
| 130 if before_sym.aliases: |
| 131 matched_before_aliases.add(id(before_sym.aliases)) |
| 132 similar.append( |
| 133 _DiffSymbol(before_sym, after_sym, diffed_symbol_by_after_aliases, |
| 134 padding_by_section_name)) |
| 135 else: |
| 136 unmatched_after_syms.append(after_sym) |
| 137 continue |
| 138 |
| 139 # Step 2: Copy symbols only in "after" (being careful with aliases). |
| 140 added = _CloneUnmatched(unmatched_after_syms, diffed_symbol_by_after_aliases) |
| 141 |
| 142 # Step 3: Negate symbols only in "before" (being careful with aliases). |
| 143 removed = [] |
| 144 negated_symbol_by_before_aliases = {} |
| 145 for remaining_syms in before_symbols_by_key.itervalues(): |
| 146 removed.extend(_NegateAndClone(remaining_syms, matched_before_aliases, |
| 147 negated_symbol_by_before_aliases)) |
| 148 |
| 149 # Step 4: Create ** symbols to represent padding differences. |
| 150 for section_name, padding in padding_by_section_name.iteritems(): |
| 151 if padding != 0: |
| 152 similar.append(models.Symbol( |
| 153 section_name, padding, |
| 154 name="** aggregate padding of diff'ed symbols")) |
| 155 return models.SymbolDiff( |
| 156 added, removed, similar, name=after.name, full_name=after.full_name, |
| 157 section_name=after.section_name) |
| 158 |
| 159 |
| 160 def Diff(before, after): |
| 161 """Diffs two SizeInfo or SymbolGroup objects. |
| 162 |
| 163 When diffing SizeInfos, a SizeInfoDiff is returned. |
| 164 When diffing SymbolGroups, a SymbolDiff is returned. |
| 165 |
| 166 Returns: |
| 167 Returns a SizeInfo when args are of type SizeInfo. |
| 168 Returns a SymbolDiff when args are of type SymbolGroup. |
| 169 """ |
| 170 if isinstance(after, models.SizeInfo): |
| 171 assert isinstance(before, models.SizeInfo) |
| 172 section_sizes = {k: after.section_sizes[k] - v |
| 173 for k, v in before.section_sizes.iteritems()} |
| 174 symbol_diff = _DiffSymbolGroups(before.symbols, after.symbols) |
| 175 return models.SizeInfoDiff(section_sizes, symbol_diff, before.metadata, |
| 176 after.metadata) |
| 177 |
| 178 assert (isinstance(after, models.SymbolGroup) and |
| 179 isinstance(before, models.SymbolGroup)) |
| 180 return _DiffSymbolGroups(before, after) |
| 181 |
| 182 |
| OLD | NEW |