summaryrefslogtreecommitdiffstats
path: root/Tools/scripts/smelly.py
blob: 43d091654d2dc84e499e1950cc3a0303879d4d33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env python
# Script checking that all symbols exported by libpython start with Py or _Py

import subprocess
import sys
import sysconfig


def get_exported_symbols():
    LIBRARY = sysconfig.get_config_var('LIBRARY')
    if not LIBRARY:
        raise Exception("failed to get LIBRARY")

    args = ('nm', '-p', LIBRARY)
    print("+ %s" % ' '.join(args))
    proc = subprocess.run(args, stdout=subprocess.PIPE, universal_newlines=True)
    if proc.returncode:
        sys.stdout.write(proc.stdout)
        sys.exit(proc.returncode)

    stdout = proc.stdout.rstrip()
    if not stdout:
        raise Exception("command output is empty")
    return stdout


def get_smelly_symbols(stdout):
    symbols = []
    ignored_symtypes = set()

    allowed_prefixes = ('Py', '_Py')
    if sys.platform == 'darwin':
        allowed_prefixes += ('__Py',)

    for line in stdout.splitlines():
        # Split line '0000000000001b80 D PyTextIOWrapper_Type'
        if not line:
            continue

        parts = line.split(maxsplit=2)
        if len(parts) < 3:
            continue

        symtype = parts[1].strip()
        # Ignore private symbols.
        #
        # If lowercase, the symbol is usually local; if uppercase, the symbol
        # is global (external).  There are however a few lowercase symbols that
        # are shown for special global symbols ("u", "v" and "w").
        if symtype.islower() and symtype not in "uvw":
            ignored_symtypes.add(symtype)
            continue

        symbol = parts[-1]
        if symbol.startswith(allowed_prefixes):
            continue
        symbol = '%s (type: %s)' % (symbol, symtype)
        symbols.append(symbol)

    if ignored_symtypes:
        print("Ignored symbol types: %s" % ', '.join(sorted(ignored_symtypes)))
        print()
    return symbols


def main():
    nm_output = get_exported_symbols()
    symbols = get_smelly_symbols(nm_output)

    if not symbols:
        print("OK: no smelly symbol found")
        sys.exit(0)

    symbols.sort()
    for symbol in symbols:
        print("Smelly symbol: %s" % symbol)
    print()
    print("ERROR: Found %s smelly symbols!" % len(symbols))
    sys.exit(1)


if __name__ == "__main__":
    main()