1# -*- Python -*- vim: set syntax=python tabstop=4 expandtab cc=80:
2#===----------------------------------------------------------------------===##
3#
4#                     The LLVM Compiler Infrastructure
5#
6# This file is dual licensed under the MIT and the University of Illinois Open
7# Source Licenses. See LICENSE.TXT for details.
8#
9#===----------------------------------------------------------------------===##
10"""
11diff - A set of functions for diff-ing two symbol lists.
12"""
13
14from libcxx.sym_check import util
15
16
17def _symbol_difference(lhs, rhs):
18    lhs_names = set((n['name'] for n in lhs))
19    rhs_names = set((n['name'] for n in rhs))
20    diff_names = lhs_names - rhs_names
21    return [n for n in lhs if n['name'] in diff_names]
22
23
24def _find_by_key(sym_list, k):
25    for sym in sym_list:
26        if sym['name'] == k:
27            return sym
28    return None
29
30
31def added_symbols(old, new):
32    return _symbol_difference(new, old)
33
34
35def removed_symbols(old, new):
36    return _symbol_difference(old, new)
37
38
39def changed_symbols(old, new):
40    changed = []
41    for old_sym in old:
42        if old_sym in new:
43            continue
44        new_sym = _find_by_key(new, old_sym['name'])
45        if (new_sym is not None and not new_sym in old
46                and old_sym != new_sym):
47            changed += [(old_sym, new_sym)]
48    return changed
49
50
51def diff(old, new):
52    added = added_symbols(old, new)
53    removed = removed_symbols(old, new)
54    changed = changed_symbols(old, new)
55    return added, removed, changed
56
57
58def report_diff(added_syms, removed_syms, changed_syms, names_only=False,
59                demangle=True):
60    def maybe_demangle(name):
61        return util.demangle_symbol(name) if demangle else name
62
63    report = ''
64    for sym in added_syms:
65        report += 'Symbol added: %s\n' % maybe_demangle(sym['name'])
66        if not names_only:
67            report += '    %s\n\n' % sym
68    if added_syms and names_only:
69        report += '\n'
70    for sym in removed_syms:
71        report += 'SYMBOL REMOVED: %s\n' % maybe_demangle(sym['name'])
72        if not names_only:
73            report += '    %s\n\n' % sym
74    if removed_syms and names_only:
75        report += '\n'
76    if not names_only:
77        for sym_pair in changed_syms:
78            old_sym, new_sym = sym_pair
79            old_str = '\n    OLD SYMBOL: %s' % old_sym
80            new_str = '\n    NEW SYMBOL: %s' % new_sym
81            report += ('SYMBOL CHANGED: %s%s%s\n\n' %
82                       (maybe_demangle(old_sym['name']),
83                        old_str, new_str))
84
85    added = bool(len(added_syms) != 0)
86    abi_break = bool(len(removed_syms))
87    if not names_only:
88        abi_break = abi_break or len(changed_syms)
89    if added or abi_break:
90        report += 'Summary\n'
91        report += '    Added:   %d\n' % len(added_syms)
92        report += '    Removed: %d\n' % len(removed_syms)
93        if not names_only:
94            report += '    Changed: %d\n' % len(changed_syms)
95        if not abi_break:
96            report += 'Symbols added.'
97        else:
98            report += 'ABI BREAKAGE: SYMBOLS ADDED OR REMOVED!'
99    else:
100        report += 'Symbols match.'
101    is_different = abi_break or bool(len(added_syms)) \
102                   or bool(len(changed_syms))
103    return report, abi_break, is_different
104