blob: 276a5ab2cc84c664e202d1343ba6c75cf3feed64 [file] [log] [blame]
Victor Stinner87d332d2017-10-24 01:29:53 -07001#!/usr/bin/env python
2# Script checking that all symbols exported by libpython start with Py or _Py
3
Victor Stinnerac7d0162020-11-24 13:38:08 +01004import os.path
Victor Stinner87d332d2017-10-24 01:29:53 -07005import subprocess
6import sys
7import sysconfig
8
9
Victor Stinnerac7d0162020-11-24 13:38:08 +010010ALLOWED_PREFIXES = ('Py', '_Py')
11if sys.platform == 'darwin':
12 ALLOWED_PREFIXES += ('__Py',)
Victor Stinner87d332d2017-10-24 01:29:53 -070013
Victor Stinnerac7d0162020-11-24 13:38:08 +010014IGNORED_EXTENSION = "_ctypes_test"
15# Ignore constructor and destructor functions
16IGNORED_SYMBOLS = {'_init', '_fini'}
17
18
19def 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
37def 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 Stinner87d332d2017-10-24 01:29:53 -070045 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
57def get_smelly_symbols(stdout):
Victor Stinnerac7d0162020-11-24 13:38:08 +010058 smelly_symbols = []
59 python_symbols = []
60 local_symbols = []
Antoine Pitroud7687eb2018-02-27 21:40:37 +010061
Victor Stinner87d332d2017-10-24 01:29:53 -070062 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 Stinner87d332d2017-10-24 01:29:53 -070072 symbol = parts[-1]
Victor Stinnerac7d0162020-11-24 13:38:08 +010073 result = '%s (type: %s)' % (symbol, symtype)
Victor Stinner87d332d2017-10-24 01:29:53 -070074
Victor Stinnerac7d0162020-11-24 13:38:08 +010075 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
91def 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
109def check_extensions():
110 print(__file__)
Miss Islington (bot)327c7642021-11-23 02:32:31 -0800111 # This assumes pybuilddir.txt is in same directory as pyconfig.h.
112 # In the case of out-of-tree builds, we can't assume pybuilddir.txt is
113 # in the source folder.
114 config_dir = os.path.dirname(sysconfig.get_config_h_filename())
115 filename = os.path.join(config_dir, "pybuilddir.txt")
Victor Stinnerac7d0162020-11-24 13:38:08 +0100116 try:
117 with open(filename, encoding="utf-8") as fp:
118 pybuilddir = fp.readline()
119 except FileNotFoundError:
120 print(f"Cannot check extensions because {filename} does not exist")
121 return True
122
123 print(f"Check extension modules from {pybuilddir} directory")
Miss Islington (bot)327c7642021-11-23 02:32:31 -0800124 builddir = os.path.join(config_dir, pybuilddir)
Victor Stinnerac7d0162020-11-24 13:38:08 +0100125 nsymbol = 0
126 for name in os.listdir(builddir):
127 if not name.endswith(".so"):
128 continue
129 if IGNORED_EXTENSION in name:
130 print()
131 print(f"Ignore extension: {name}")
132 continue
133
Victor Stinner87d332d2017-10-24 01:29:53 -0700134 print()
Victor Stinnerac7d0162020-11-24 13:38:08 +0100135 filename = os.path.join(builddir, name)
136 nsymbol += check_library(filename, dynamic=True)
137
138 return nsymbol
Victor Stinner87d332d2017-10-24 01:29:53 -0700139
140
141def main():
Victor Stinner801bb0b2021-02-17 11:14:42 +0100142 nsymbol = 0
143
Victor Stinnerac7d0162020-11-24 13:38:08 +0100144 # static library
145 LIBRARY = sysconfig.get_config_var('LIBRARY')
146 if not LIBRARY:
147 raise Exception("failed to get LIBRARY variable from sysconfig")
Victor Stinner801bb0b2021-02-17 11:14:42 +0100148 if os.path.exists(LIBRARY):
149 nsymbol += check_library(LIBRARY)
Victor Stinner87d332d2017-10-24 01:29:53 -0700150
Victor Stinnerac7d0162020-11-24 13:38:08 +0100151 # dynamic library
152 LDLIBRARY = sysconfig.get_config_var('LDLIBRARY')
153 if not LDLIBRARY:
154 raise Exception("failed to get LDLIBRARY variable from sysconfig")
155 if LDLIBRARY != LIBRARY:
156 print()
157 nsymbol += check_library(LDLIBRARY, dynamic=True)
Victor Stinner87d332d2017-10-24 01:29:53 -0700158
Victor Stinnerac7d0162020-11-24 13:38:08 +0100159 # Check extension modules like _ssl.cpython-310d-x86_64-linux-gnu.so
160 nsymbol += check_extensions()
161
162 if nsymbol:
163 print()
164 print(f"ERROR: Found {nsymbol} smelly symbols in total!")
165 sys.exit(1)
166
Victor Stinner87d332d2017-10-24 01:29:53 -0700167 print()
Victor Stinnerac7d0162020-11-24 13:38:08 +0100168 print(f"OK: all exported symbols of all libraries "
169 f"are prefixed with {' or '.join(map(repr, ALLOWED_PREFIXES))}")
Victor Stinner87d332d2017-10-24 01:29:53 -0700170
171
172if __name__ == "__main__":
173 main()