blob: fb01660dea33ae9fc1008e5cdc1c365687925bc0 [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__)
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 Stinner87d332d2017-10-24 01:29:53 -0700131 print()
Victor Stinnerac7d0162020-11-24 13:38:08 +0100132 filename = os.path.join(builddir, name)
133 nsymbol += check_library(filename, dynamic=True)
134
135 return nsymbol
Victor Stinner87d332d2017-10-24 01:29:53 -0700136
137
138def main():
Victor Stinner801bb0b2021-02-17 11:14:42 +0100139 nsymbol = 0
140
Victor Stinnerac7d0162020-11-24 13:38:08 +0100141 # static library
142 LIBRARY = sysconfig.get_config_var('LIBRARY')
143 if not LIBRARY:
144 raise Exception("failed to get LIBRARY variable from sysconfig")
Victor Stinner801bb0b2021-02-17 11:14:42 +0100145 if os.path.exists(LIBRARY):
146 nsymbol += check_library(LIBRARY)
Victor Stinner87d332d2017-10-24 01:29:53 -0700147
Victor Stinnerac7d0162020-11-24 13:38:08 +0100148 # 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 Stinner87d332d2017-10-24 01:29:53 -0700155
Victor Stinnerac7d0162020-11-24 13:38:08 +0100156 # 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 Stinner87d332d2017-10-24 01:29:53 -0700164 print()
Victor Stinnerac7d0162020-11-24 13:38:08 +0100165 print(f"OK: all exported symbols of all libraries "
166 f"are prefixed with {' or '.join(map(repr, ALLOWED_PREFIXES))}")
Victor Stinner87d332d2017-10-24 01:29:53 -0700167
168
169if __name__ == "__main__":
170 main()