bpo-31810: Add smelly.py to check exported symbols (#4057)

* Add Tools/scripts/smelly.py: script checking if all symbols
  exported by libpython start with "Py" or "_Py".
* Modify "make smelly" to run smelly.py: the command now fails with a
  non-zero exit code if libpython leaks a "smelly" symbol.
* Travis CI now runs "make smelly"
This commit is contained in:
Victor Stinner 2017-10-24 01:29:53 -07:00 committed by GitHub
parent 1aa00ff383
commit 87d332dcdb
3 changed files with 82 additions and 3 deletions

View File

@ -91,6 +91,8 @@ script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./python Tools/scripts/patchcheck.py --travis $TRAVIS_PULL_REQUEST; fi
# `-r -w` implicitly provided through `make buildbottest`.
- make buildbottest TESTOPTS="-j4 -uall,-cpu"
# Check that all symbols exported by libpython start with "Py" or "_Py"
- make smelly
notifications:
email: false

View File

@ -1659,10 +1659,9 @@ distclean: clobber
-o -name '*.bak' ')' \
-exec rm -f {} ';'
# Check for smelly exported symbols (not starting with Py/_Py)
# Check that all symbols exported by libpython start with "Py" or "_Py"
smelly: @DEF_MAKE_RULE@
nm -p $(LIBRARY) | \
sed -n "/ [TDB] /s/.* //p" | grep -v "^_*Py" | sort -u; \
$(RUNSHARED) ./$(BUILDPYTHON) Tools/scripts/smelly.py
# Find files with funny names
funny:

78
Tools/scripts/smelly.py Executable file
View File

@ -0,0 +1,78 @@
#!/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()
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(('Py', '_Py')):
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()