Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
78 changes: 78 additions & 0 deletions Tools/scripts/smelly.py
Original file line number Diff line number Diff line change
@@ -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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stdout is not a very informative name here, and somewhat confusing in the body of the function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, renamed to nm_output.

symbols = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have this function be a generator and just yield the smelly symbols? Just style preference so feel free to ignore.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer a list :-)

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()