Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/build/smelly.py
12 views
1
#!/usr/bin/env python
2
# Script checking that all symbols exported by libpython start with Py or _Py
3
4
import os.path
5
import subprocess
6
import sys
7
import sysconfig
8
9
10
ALLOWED_PREFIXES = ('Py', '_Py')
11
if sys.platform == 'darwin':
12
ALLOWED_PREFIXES += ('__Py',)
13
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)
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):
58
smelly_symbols = []
59
python_symbols = []
60
local_symbols = []
61
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()
72
symbol = parts[-1]
73
result = '%s (type: %s)' % (symbol, symtype)
74
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
# 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")
116
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")
124
builddir = os.path.join(config_dir, pybuilddir)
125
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
134
print()
135
filename = os.path.join(builddir, name)
136
nsymbol += check_library(filename, dynamic=True)
137
138
return nsymbol
139
140
141
def main():
142
nsymbol = 0
143
144
# static library
145
LIBRARY = sysconfig.get_config_var('LIBRARY')
146
if not LIBRARY:
147
raise Exception("failed to get LIBRARY variable from sysconfig")
148
if os.path.exists(LIBRARY):
149
nsymbol += check_library(LIBRARY)
150
151
# 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)
158
159
# 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
167
print()
168
print(f"OK: all exported symbols of all libraries "
169
f"are prefixed with {' or '.join(map(repr, ALLOWED_PREFIXES))}")
170
171
172
if __name__ == "__main__":
173
main()
174
175