Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Script checking that all symbols exported by libpython start with Py or _Py |
| 3 | |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 4 | import os.path |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 5 | import subprocess |
| 6 | import sys |
| 7 | import sysconfig |
| 8 | |
| 9 | |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 10 | ALLOWED_PREFIXES = ('Py', '_Py') |
| 11 | if sys.platform == 'darwin': |
| 12 | ALLOWED_PREFIXES += ('__Py',) |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 13 | |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 14 | IGNORED_EXTENSION = "_ctypes_test" |
| 15 | # Ignore constructor and destructor functions |
| 16 | IGNORED_SYMBOLS = {'_init', '_fini'} |
| 17 | |
| 18 | |
| 19 | def is_local_symbol_type(symtype): |
| 20 | # Ignore local symbols. |
| 21 | |
| 22 | # If lowercase, the symbol is usually local; if uppercase, the symbol |
| 23 | # is global (external). There are however a few lowercase symbols that |
| 24 | # are shown for special global symbols ("u", "v" and "w"). |
| 25 | if symtype.islower() and symtype not in "uvw": |
| 26 | return True |
| 27 | |
| 28 | # Ignore the initialized data section (d and D) and the BSS data |
| 29 | # section. For example, ignore "__bss_start (type: B)" |
| 30 | # and "_edata (type: D)". |
| 31 | if symtype in "bBdD": |
| 32 | return True |
| 33 | |
| 34 | return False |
| 35 | |
| 36 | |
| 37 | def get_exported_symbols(library, dynamic=False): |
| 38 | print(f"Check that {library} only exports symbols starting with Py or _Py") |
| 39 | |
| 40 | # Only look at dynamic symbols |
| 41 | args = ['nm', '--no-sort'] |
| 42 | if dynamic: |
| 43 | args.append('--dynamic') |
| 44 | args.append(library) |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 45 | print("+ %s" % ' '.join(args)) |
| 46 | proc = subprocess.run(args, stdout=subprocess.PIPE, universal_newlines=True) |
| 47 | if proc.returncode: |
| 48 | sys.stdout.write(proc.stdout) |
| 49 | sys.exit(proc.returncode) |
| 50 | |
| 51 | stdout = proc.stdout.rstrip() |
| 52 | if not stdout: |
| 53 | raise Exception("command output is empty") |
| 54 | return stdout |
| 55 | |
| 56 | |
| 57 | def get_smelly_symbols(stdout): |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 58 | smelly_symbols = [] |
| 59 | python_symbols = [] |
| 60 | local_symbols = [] |
Antoine Pitrou | d7687eb | 2018-02-27 21:40:37 +0100 | [diff] [blame] | 61 | |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 62 | for line in stdout.splitlines(): |
| 63 | # Split line '0000000000001b80 D PyTextIOWrapper_Type' |
| 64 | if not line: |
| 65 | continue |
| 66 | |
| 67 | parts = line.split(maxsplit=2) |
| 68 | if len(parts) < 3: |
| 69 | continue |
| 70 | |
| 71 | symtype = parts[1].strip() |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 72 | symbol = parts[-1] |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 73 | result = '%s (type: %s)' % (symbol, symtype) |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 74 | |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 75 | if symbol.startswith(ALLOWED_PREFIXES): |
| 76 | python_symbols.append(result) |
| 77 | continue |
| 78 | |
| 79 | if is_local_symbol_type(symtype): |
| 80 | local_symbols.append(result) |
| 81 | elif symbol in IGNORED_SYMBOLS: |
| 82 | local_symbols.append(result) |
| 83 | else: |
| 84 | smelly_symbols.append(result) |
| 85 | |
| 86 | if local_symbols: |
| 87 | print(f"Ignore {len(local_symbols)} local symbols") |
| 88 | return smelly_symbols, python_symbols |
| 89 | |
| 90 | |
| 91 | def check_library(library, dynamic=False): |
| 92 | nm_output = get_exported_symbols(library, dynamic) |
| 93 | smelly_symbols, python_symbols = get_smelly_symbols(nm_output) |
| 94 | |
| 95 | if not smelly_symbols: |
| 96 | print(f"OK: no smelly symbol found ({len(python_symbols)} Python symbols)") |
| 97 | return 0 |
| 98 | |
| 99 | print() |
| 100 | smelly_symbols.sort() |
| 101 | for symbol in smelly_symbols: |
| 102 | print("Smelly symbol: %s" % symbol) |
| 103 | |
| 104 | print() |
| 105 | print("ERROR: Found %s smelly symbols!" % len(smelly_symbols)) |
| 106 | return len(smelly_symbols) |
| 107 | |
| 108 | |
| 109 | def check_extensions(): |
| 110 | print(__file__) |
| 111 | srcdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) |
| 112 | filename = os.path.join(srcdir, "pybuilddir.txt") |
| 113 | try: |
| 114 | with open(filename, encoding="utf-8") as fp: |
| 115 | pybuilddir = fp.readline() |
| 116 | except FileNotFoundError: |
| 117 | print(f"Cannot check extensions because {filename} does not exist") |
| 118 | return True |
| 119 | |
| 120 | print(f"Check extension modules from {pybuilddir} directory") |
| 121 | builddir = os.path.join(srcdir, pybuilddir) |
| 122 | nsymbol = 0 |
| 123 | for name in os.listdir(builddir): |
| 124 | if not name.endswith(".so"): |
| 125 | continue |
| 126 | if IGNORED_EXTENSION in name: |
| 127 | print() |
| 128 | print(f"Ignore extension: {name}") |
| 129 | continue |
| 130 | |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 131 | print() |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 132 | filename = os.path.join(builddir, name) |
| 133 | nsymbol += check_library(filename, dynamic=True) |
| 134 | |
| 135 | return nsymbol |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 136 | |
| 137 | |
| 138 | def main(): |
Victor Stinner | 801bb0b | 2021-02-17 11:14:42 +0100 | [diff] [blame] | 139 | nsymbol = 0 |
| 140 | |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 141 | # static library |
| 142 | LIBRARY = sysconfig.get_config_var('LIBRARY') |
| 143 | if not LIBRARY: |
| 144 | raise Exception("failed to get LIBRARY variable from sysconfig") |
Victor Stinner | 801bb0b | 2021-02-17 11:14:42 +0100 | [diff] [blame] | 145 | if os.path.exists(LIBRARY): |
| 146 | nsymbol += check_library(LIBRARY) |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 147 | |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 148 | # dynamic library |
| 149 | LDLIBRARY = sysconfig.get_config_var('LDLIBRARY') |
| 150 | if not LDLIBRARY: |
| 151 | raise Exception("failed to get LDLIBRARY variable from sysconfig") |
| 152 | if LDLIBRARY != LIBRARY: |
| 153 | print() |
| 154 | nsymbol += check_library(LDLIBRARY, dynamic=True) |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 155 | |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 156 | # Check extension modules like _ssl.cpython-310d-x86_64-linux-gnu.so |
| 157 | nsymbol += check_extensions() |
| 158 | |
| 159 | if nsymbol: |
| 160 | print() |
| 161 | print(f"ERROR: Found {nsymbol} smelly symbols in total!") |
| 162 | sys.exit(1) |
| 163 | |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 164 | print() |
Victor Stinner | ac7d016 | 2020-11-24 13:38:08 +0100 | [diff] [blame] | 165 | print(f"OK: all exported symbols of all libraries " |
| 166 | f"are prefixed with {' or '.join(map(repr, ALLOWED_PREFIXES))}") |
Victor Stinner | 87d332d | 2017-10-24 01:29:53 -0700 | [diff] [blame] | 167 | |
| 168 | |
| 169 | if __name__ == "__main__": |
| 170 | main() |