summaryrefslogtreecommitdiffstats
path: root/Tools/c-globals
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2017-09-08 05:51:28 (GMT)
committerGitHub <noreply@github.com>2017-09-08 05:51:28 (GMT)
commit2ebc5ce42a8a9e047e790aefbf9a94811569b2b6 (patch)
treef8c483f24e0d1ee43ac5cc9ad82d2ee7cccf69d2 /Tools/c-globals
parentbab21faded31c70b142776b9a6075a4cda055d7f (diff)
downloadcpython-2ebc5ce42a8a9e047e790aefbf9a94811569b2b6.zip
cpython-2ebc5ce42a8a9e047e790aefbf9a94811569b2b6.tar.gz
cpython-2ebc5ce42a8a9e047e790aefbf9a94811569b2b6.tar.bz2
bpo-30860: Consolidate stateful runtime globals. (#3397)
* group the (stateful) runtime globals into various topical structs * consolidate the topical structs under a single top-level _PyRuntimeState struct * add a check-c-globals.py script that helps identify runtime globals Other globals are excluded (see globals.txt and check-c-globals.py).
Diffstat (limited to 'Tools/c-globals')
-rw-r--r--Tools/c-globals/README41
-rw-r--r--Tools/c-globals/check-c-globals.py446
-rw-r--r--Tools/c-globals/ignored-globals.txt494
3 files changed, 981 insertions, 0 deletions
diff --git a/Tools/c-globals/README b/Tools/c-globals/README
new file mode 100644
index 0000000..d0e6e8e
--- /dev/null
+++ b/Tools/c-globals/README
@@ -0,0 +1,41 @@
+#######################################
+# C Globals and CPython Runtime State.
+
+CPython's C code makes extensive use of global variables. Each global
+falls into one of several categories:
+
+* (effectively) constants (incl. static types)
+* globals used exclusively in main or in the REPL
+* freelists, caches, and counters
+* process-global state
+* module state
+* Python runtime state
+
+The ignored-globals.txt file is organized similarly. Of the different
+categories, the last two are problematic and generally should not exist
+in the codebase.
+
+Globals that hold module state (i.e. in Modules/*.c) cause problems
+when multiple interpreters are in use. For more info, see PEP 3121,
+which addresses the situation for extension modules in general.
+
+Globals in the last category should be avoided as well. The problem
+isn't with the Python runtime having state. Rather, the problem is with
+that state being spread thoughout the codebase in dozens of individual
+globals. Unlike the other globals, the runtime state represents a set
+of values that are constantly shifting in a complex way. When they are
+spread out it's harder to get a clear picture of what the runtime
+involves. Furthermore, when they are spread out it complicates efforts
+that change the runtime.
+
+Consequently, the globals for Python's runtime state have been
+consolidated under a single top-level _PyRuntime global. No new globals
+should be added for runtime state. Instead, they should be added to
+_PyRuntimeState or one of its sub-structs. The check-c-globals script
+should be run to ensure that no new globals have been added:
+
+ python3 Tools/c-globals/check-c-globals.py
+
+If it reports any globals then they should be resolved. If the globals
+are runtime state then they should be folded into _PyRuntimeState.
+Otherwise they should be added to ignored-globals.txt.
diff --git a/Tools/c-globals/check-c-globals.py b/Tools/c-globals/check-c-globals.py
new file mode 100644
index 0000000..1de69a8
--- /dev/null
+++ b/Tools/c-globals/check-c-globals.py
@@ -0,0 +1,446 @@
+
+from collections import namedtuple
+import glob
+import os.path
+import re
+import shutil
+import sys
+import subprocess
+
+
+VERBOSITY = 2
+
+C_GLOBALS_DIR = os.path.abspath(os.path.dirname(__file__))
+TOOLS_DIR = os.path.dirname(C_GLOBALS_DIR)
+ROOT_DIR = os.path.dirname(TOOLS_DIR)
+GLOBALS_FILE = os.path.join(C_GLOBALS_DIR, 'ignored-globals.txt')
+
+SOURCE_DIRS = ['Include', 'Objects', 'Modules', 'Parser', 'Python']
+
+CAPI_REGEX = re.compile(r'^ *PyAPI_DATA\([^)]*\) \W*(_?Py\w+(?:, \w+)*\w).*;.*$')
+
+
+IGNORED_VARS = {
+ '_DYNAMIC',
+ '_GLOBAL_OFFSET_TABLE_',
+ '__JCR_LIST__',
+ '__JCR_END__',
+ '__TMC_END__',
+ '__bss_start',
+ '__data_start',
+ '__dso_handle',
+ '_edata',
+ '_end',
+ }
+
+
+def find_capi_vars(root):
+ capi_vars = {}
+ for dirname in SOURCE_DIRS:
+ for filename in glob.glob(os.path.join(ROOT_DIR, dirname, '**/*.[hc]'),
+ recursive=True):
+ with open(filename) as file:
+ for name in _find_capi_vars(file):
+ if name in capi_vars:
+ assert not filename.endswith('.c')
+ assert capi_vars[name].endswith('.c')
+ capi_vars[name] = filename
+ return capi_vars
+
+
+def _find_capi_vars(lines):
+ for line in lines:
+ if not line.startswith('PyAPI_DATA'):
+ continue
+ assert '{' not in line
+ match = CAPI_REGEX.match(line)
+ assert match
+ names, = match.groups()
+ for name in names.split(', '):
+ yield name
+
+
+def _read_global_names(filename):
+ # These variables are shared between all interpreters in the process.
+ with open(filename) as file:
+ return {line.partition('#')[0].strip()
+ for line in file
+ if line.strip() and not line.startswith('#')}
+
+
+def _is_global_var(name, globalnames):
+ if _is_autogen_var(name):
+ return True
+ if _is_type_var(name):
+ return True
+ if _is_module(name):
+ return True
+ if _is_exception(name):
+ return True
+ if _is_compiler(name):
+ return True
+ return name in globalnames
+
+
+def _is_autogen_var(name):
+ return (
+ name.startswith('PyId_') or
+ '.' in name or
+ # Objects/typeobject.c
+ name.startswith('op_id.') or
+ name.startswith('rop_id.') or
+ # Python/graminit.c
+ name.startswith('arcs_') or
+ name.startswith('states_')
+ )
+
+
+def _is_type_var(name):
+ if name.endswith(('Type', '_Type', '_type')): # XXX Always a static type?
+ return True
+ if name.endswith('_desc'): # for structseq types
+ return True
+ return (
+ name.startswith('doc_') or
+ name.endswith(('_doc', '__doc__', '_docstring')) or
+ name.endswith('_methods') or
+ name.endswith('_fields') or
+ name.endswith(('_memberlist', '_members')) or
+ name.endswith('_slots') or
+ name.endswith(('_getset', '_getsets', '_getsetlist')) or
+ name.endswith('_as_mapping') or
+ name.endswith('_as_number') or
+ name.endswith('_as_sequence') or
+ name.endswith('_as_buffer') or
+ name.endswith('_as_async')
+ )
+
+
+def _is_module(name):
+ if name.endswith(('_functions', 'Methods', '_Methods')):
+ return True
+ if name == 'module_def':
+ return True
+ if name == 'initialized':
+ return True
+ return name.endswith(('module', '_Module'))
+
+
+def _is_exception(name):
+ # Other vars are enumerated in globals-core.txt.
+ if not name.startswith(('PyExc_', '_PyExc_')):
+ return False
+ return name.endswith(('Error', 'Warning'))
+
+
+def _is_compiler(name):
+ return (
+ # Python/Pythyon-ast.c
+ name.endswith('_type') or
+ name.endswith('_singleton') or
+ name.endswith('_attributes')
+ )
+
+
+class Var(namedtuple('Var', 'name kind scope capi filename')):
+
+ @classmethod
+ def parse_nm(cls, line, expected, ignored, capi_vars, globalnames):
+ _, _, line = line.partition(' ') # strip off the address
+ line = line.strip()
+ kind, _, line = line.partition(' ')
+ if kind in ignored or ():
+ return None
+ elif kind not in expected or ():
+ raise RuntimeError('unsupported NM type {!r}'.format(kind))
+
+ name, _, filename = line.partition('\t')
+ name = name.strip()
+ if _is_autogen_var(name):
+ return None
+ if _is_global_var(name, globalnames):
+ scope = 'global'
+ else:
+ scope = None
+ capi = (name in capi_vars or ())
+ if filename:
+ filename = os.path.relpath(filename.partition(':')[0])
+ return cls(name, kind, scope, capi, filename or '~???~')
+
+ @property
+ def external(self):
+ return self.kind.isupper()
+
+
+def find_vars(root, globals_filename=GLOBALS_FILE):
+ python = os.path.join(root, 'python')
+ if not os.path.exists(python):
+ raise RuntimeError('python binary missing (need to build it first?)')
+ capi_vars = find_capi_vars(root)
+ globalnames = _read_global_names(globals_filename)
+
+ nm = shutil.which('nm')
+ if nm is None:
+ # XXX Use dumpbin.exe /SYMBOLS on Windows.
+ raise NotImplementedError
+ else:
+ yield from (var
+ for var in _find_var_symbols(python, nm, capi_vars,
+ globalnames)
+ if var.name not in IGNORED_VARS)
+
+
+NM_FUNCS = set('Tt')
+NM_PUBLIC_VARS = set('BD')
+NM_PRIVATE_VARS = set('bd')
+NM_VARS = NM_PUBLIC_VARS | NM_PRIVATE_VARS
+NM_DATA = set('Rr')
+NM_OTHER = set('ACGgiINpSsuUVvWw-?')
+NM_IGNORED = NM_FUNCS | NM_DATA | NM_OTHER
+
+
+def _find_var_symbols(python, nm, capi_vars, globalnames):
+ args = [nm,
+ '--line-numbers',
+ python]
+ out = subprocess.check_output(args)
+ for line in out.decode('utf-8').splitlines():
+ var = Var.parse_nm(line, NM_VARS, NM_IGNORED, capi_vars, globalnames)
+ if var is None:
+ continue
+ yield var
+
+
+#######################################
+
+class Filter(namedtuple('Filter', 'name op value action')):
+
+ @classmethod
+ def parse(cls, raw):
+ action = '+'
+ if raw.startswith(('+', '-')):
+ action = raw[0]
+ raw = raw[1:]
+ # XXX Support < and >?
+ name, op, value = raw.partition('=')
+ return cls(name, op, value, action)
+
+ def check(self, var):
+ value = getattr(var, self.name, None)
+ if not self.op:
+ matched = bool(value)
+ elif self.op == '=':
+ matched = (value == self.value)
+ else:
+ raise NotImplementedError
+
+ if self.action == '+':
+ return matched
+ elif self.action == '-':
+ return not matched
+ else:
+ raise NotImplementedError
+
+
+def filter_var(var, filters):
+ for filter in filters:
+ if not filter.check(var):
+ return False
+ return True
+
+
+def make_sort_key(spec):
+ columns = [(col.strip('_'), '_' if col.startswith('_') else '')
+ for col in spec]
+ def sort_key(var):
+ return tuple(getattr(var, col).lstrip(prefix)
+ for col, prefix in columns)
+ return sort_key
+
+
+def make_groups(allvars, spec):
+ group = spec
+ groups = {}
+ for var in allvars:
+ value = getattr(var, group)
+ key = '{}: {}'.format(group, value)
+ try:
+ groupvars = groups[key]
+ except KeyError:
+ groupvars = groups[key] = []
+ groupvars.append(var)
+ return groups
+
+
+def format_groups(groups, columns, fmts, widths):
+ for group in sorted(groups):
+ groupvars = groups[group]
+ yield '', 0
+ yield ' # {}'.format(group), 0
+ yield from format_vars(groupvars, columns, fmts, widths)
+
+
+def format_vars(allvars, columns, fmts, widths):
+ fmt = ' '.join(fmts[col] for col in columns)
+ fmt = ' ' + fmt.replace(' ', ' ') + ' ' # for div margin
+ header = fmt.replace(':', ':^').format(*(col.upper() for col in columns))
+ yield header, 0
+ div = ' '.join('-'*(widths[col]+2) for col in columns)
+ yield div, 0
+ for var in allvars:
+ values = (getattr(var, col) for col in columns)
+ row = fmt.format(*('X' if val is True else val or ''
+ for val in values))
+ yield row, 1
+ yield div, 0
+
+
+#######################################
+
+COLUMNS = 'name,external,capi,scope,filename'
+COLUMN_NAMES = COLUMNS.split(',')
+
+COLUMN_WIDTHS = {col: len(col)
+ for col in COLUMN_NAMES}
+COLUMN_WIDTHS.update({
+ 'name': 50,
+ 'scope': 7,
+ 'filename': 40,
+ })
+COLUMN_FORMATS = {col: '{:%s}' % width
+ for col, width in COLUMN_WIDTHS.items()}
+for col in COLUMN_FORMATS:
+ if COLUMN_WIDTHS[col] == len(col):
+ COLUMN_FORMATS[col] = COLUMN_FORMATS[col].replace(':', ':^')
+
+
+def _parse_filters_arg(raw, error):
+ filters = []
+ for value in raw.split(','):
+ value=value.strip()
+ if not value:
+ continue
+ try:
+ filter = Filter.parse(value)
+ if filter.name not in COLUMN_NAMES:
+ raise Exception('unsupported column {!r}'.format(filter.name))
+ except Exception as e:
+ error('bad filter {!r}: {}'.format(raw, e))
+ filters.append(filter)
+ return filters
+
+
+def _parse_columns_arg(raw, error):
+ columns = raw.split(',')
+ for column in columns:
+ if column not in COLUMN_NAMES:
+ error('unsupported column {!r}'.format(column))
+ return columns
+
+
+def _parse_sort_arg(raw, error):
+ sort = raw.split(',')
+ for column in sort:
+ if column.lstrip('_') not in COLUMN_NAMES:
+ error('unsupported column {!r}'.format(column))
+ return sort
+
+
+def _parse_group_arg(raw, error):
+ if not raw:
+ return raw
+ group = raw
+ if group not in COLUMN_NAMES:
+ error('unsupported column {!r}'.format(group))
+ if group != 'filename':
+ error('unsupported group {!r}'.format(group))
+ return group
+
+
+def parse_args(argv=None):
+ if argv is None:
+ argv = sys.argv[1:]
+
+ import argparse
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('-v', '--verbose', action='count', default=0)
+ parser.add_argument('-q', '--quiet', action='count', default=0)
+
+ parser.add_argument('--filters', default='-scope',
+ help='[[-]<COLUMN>[=<GLOB>]] ...')
+
+ parser.add_argument('--columns', default=COLUMNS,
+ help='a comma-separated list of columns to show')
+ parser.add_argument('--sort', default='filename,_name',
+ help='a comma-separated list of columns to sort')
+ parser.add_argument('--group',
+ help='group by the given column name (- to not group)')
+
+ parser.add_argument('--rc-on-match', dest='rc', type=int)
+
+ parser.add_argument('filename', nargs='?', default=GLOBALS_FILE)
+
+ args = parser.parse_args(argv)
+
+ verbose = vars(args).pop('verbose', 0)
+ quiet = vars(args).pop('quiet', 0)
+ args.verbosity = max(0, VERBOSITY + verbose - quiet)
+
+ if args.sort.startswith('filename') and not args.group:
+ args.group = 'filename'
+
+ if args.rc is None:
+ if '-scope=core' in args.filters or 'core' not in args.filters:
+ args.rc = 0
+ else:
+ args.rc = 1
+
+ args.filters = _parse_filters_arg(args.filters, parser.error)
+ args.columns = _parse_columns_arg(args.columns, parser.error)
+ args.sort = _parse_sort_arg(args.sort, parser.error)
+ args.group = _parse_group_arg(args.group, parser.error)
+
+ return args
+
+
+def main(root=ROOT_DIR, filename=GLOBALS_FILE,
+ filters=None, columns=COLUMN_NAMES, sort=None, group=None,
+ verbosity=VERBOSITY, rc=1):
+
+ log = lambda msg: ...
+ if verbosity >= 2:
+ log = lambda msg: print(msg)
+
+ allvars = (var
+ for var in find_vars(root, filename)
+ if filter_var(var, filters))
+ if sort:
+ allvars = sorted(allvars, key=make_sort_key(sort))
+
+ if group:
+ try:
+ columns.remove(group)
+ except ValueError:
+ pass
+ grouped = make_groups(allvars, group)
+ lines = format_groups(grouped, columns, COLUMN_FORMATS, COLUMN_WIDTHS)
+ else:
+ lines = format_vars(allvars, columns, COLUMN_FORMATS, COLUMN_WIDTHS)
+
+ total = 0
+ for line, count in lines:
+ total += count
+ log(line)
+ log('\ntotal: {}'.format(total))
+
+ if total and rc:
+ print('ERROR: found unsafe globals', file=sys.stderr)
+ return rc
+ return 0
+
+
+if __name__ == '__main__':
+ args = parse_args()
+ sys.exit(
+ main(**vars(args)))
diff --git a/Tools/c-globals/ignored-globals.txt b/Tools/c-globals/ignored-globals.txt
new file mode 100644
index 0000000..4fafba6
--- /dev/null
+++ b/Tools/c-globals/ignored-globals.txt
@@ -0,0 +1,494 @@
+# All variables declared here are shared between all interpreters
+# in a single process. That means that they must not be changed
+# unless that change should apply to all interpreters.
+#
+# See check-c-globals.py.
+#
+# Many generic names are handled via the script:
+#
+# * most exceptions and all warnings handled via _is_exception()
+# * for builtin modules, generic names are handled via _is_module()
+# * generic names for static types handled via _is_type_var()
+# * AST vars handled via _is_compiler()
+
+
+#######################################
+# main
+
+# Modules/getpath.c
+exec_prefix
+module_search_path
+prefix
+progpath
+
+# Modules/main.c
+orig_argc
+orig_argv
+
+# Python/getopt.c
+opt_ptr
+_PyOS_optarg
+_PyOS_opterr
+_PyOS_optind
+
+
+#######################################
+# REPL
+
+# Parser/myreadline.c
+PyOS_InputHook
+PyOS_ReadlineFunctionPointer
+_PyOS_ReadlineLock
+_PyOS_ReadlineTState
+
+
+#######################################
+# state
+
+# Python/dtoa.c
+p5s
+pmem_next # very slight race
+private_mem # very slight race
+
+# Python/import.c
+# For the moment the import lock stays global. Ultimately there should
+# be a global lock for extension modules and a per-interpreter lock.
+import_lock
+import_lock_level
+import_lock_thread
+
+# Python/pylifecycle.c
+_PyRuntime
+
+
+#---------------------------------
+# module globals (PyObject)
+
+# Modules/_functoolsmodule.c
+kwd_mark
+
+# Modules/_localemodule.c
+Error
+
+# Modules/_threadmodule.c
+ThreadError
+
+# Modules/_tracemalloc.c
+unknown_filename
+
+# Modules/gcmodule.c
+gc_str
+
+# Modules/posixmodule.c
+billion
+posix_putenv_garbage
+
+# Modules/signalmodule.c
+DefaultHandler
+IgnoreHandler
+IntHandler
+ItimerError
+
+# Modules/zipimport.c
+ZipImportError
+zip_directory_cache
+
+
+#---------------------------------
+# module globals (other)
+
+# Modules/_tracemalloc.c
+allocators
+tables_lock
+tracemalloc_config
+tracemalloc_empty_traceback
+tracemalloc_filenames
+tracemalloc_peak_traced_memory
+tracemalloc_reentrant_key
+tracemalloc_traceback
+tracemalloc_tracebacks
+tracemalloc_traced_memory
+tracemalloc_traces
+
+# Modules/faulthandler.c
+fatal_error
+faulthandler_handlers
+old_stack
+stack
+thread
+user_signals
+
+# Modules/posixmodule.c
+posix_constants_confstr
+posix_constants_pathconf
+posix_constants_sysconf
+_stat_float_times # deprecated, __main__-only
+structseq_new
+ticks_per_second
+
+# Modules/signalmodule.c
+Handlers # main thread only
+is_tripped # main thread only
+main_pid
+main_thread
+old_siginthandler
+wakeup_fd # main thread only
+
+# Modules/zipimport.c
+zip_searchorder
+
+# Python/bltinmodule.c
+Py_FileSystemDefaultEncodeErrors
+Py_FileSystemDefaultEncoding
+Py_HasFileSystemDefaultEncoding
+
+# Python/sysmodule.c
+_PySys_ImplCacheTag
+_PySys_ImplName
+
+
+#---------------------------------
+# freelists
+
+# Modules/_collectionsmodule.c
+freeblocks
+numfreeblocks
+
+# Objects/classobject.c
+free_list
+numfree
+
+# Objects/dictobject.c
+free_list
+keys_free_list
+numfree
+numfreekeys
+
+# Objects/exceptions.c
+memerrors_freelist
+memerrors_numfree
+
+# Objects/floatobject.c
+free_list
+numfree
+
+# Objects/frameobject.c
+free_list
+numfree
+
+# Objects/genobject.c
+ag_asend_freelist
+ag_asend_freelist_free
+ag_value_freelist
+ag_value_freelist_free
+
+# Objects/listobject.c
+free_list
+numfree
+
+# Objects/methodobject.c
+free_list
+numfree
+
+# Objects/sliceobject.c
+slice_cache # slight race
+
+# Objects/tupleobject.c
+free_list
+numfree
+
+# Python/dtoa.c
+freelist # very slight race
+
+
+#---------------------------------
+# caches (PyObject)
+
+# Objects/typeobject.c
+method_cache # only for static types
+next_version_tag # only for static types
+
+# Python/dynload_shlib.c
+handles # slight race during import
+nhandles # slight race during import
+
+# Python/import.c
+extensions # slight race on init during import
+
+
+#---------------------------------
+# caches (other)
+
+# Python/bootstrap_hash.c
+urandom_cache
+
+# Python/modsupport.c
+_Py_PackageContext # Slight race during import! Move to PyThreadState?
+
+
+#---------------------------------
+# counters
+
+# Objects/bytesobject.c
+null_strings
+one_strings
+
+# Objects/dictobject.c
+pydict_global_version
+
+# Objects/moduleobject.c
+max_module_number # slight race during import
+
+
+#######################################
+# constants
+
+#---------------------------------
+# singletons
+
+# Objects/boolobject.c
+_Py_FalseStruct
+_Py_TrueStruct
+
+# Objects/object.c
+_Py_NoneStruct
+_Py_NotImplementedStruct
+
+# Objects/sliceobject.c
+_Py_EllipsisObject
+
+
+#---------------------------------
+# constants (other)
+
+# Modules/config.c
+_PyImport_Inittab
+
+# Objects/bytearrayobject.c
+_PyByteArray_empty_string
+
+# Objects/dictobject.c
+empty_keys_struct
+empty_values
+
+# Objects/floatobject.c
+detected_double_format
+detected_float_format
+double_format
+float_format
+
+# Objects/longobject.c
+_PyLong_DigitValue
+
+# Objects/object.c
+_Py_SwappedOp
+
+# Objects/obmalloc.c
+_PyMem_Debug
+
+# Objects/setobject.c
+_dummy_struct
+
+# Objects/structseq.c
+PyStructSequence_UnnamedField
+
+# Objects/typeobject.c
+name_op
+slotdefs # almost
+slotdefs_initialized # almost
+subtype_getsets_dict_only
+subtype_getsets_full
+subtype_getsets_weakref_only
+tp_new_methoddef
+
+# Objects/unicodeobject.c
+bloom_linebreak
+static_strings # slight race
+
+# Parser/tokenizer.c
+_PyParser_TokenNames
+
+# Python/Python-ast.c
+alias_fields
+
+# Python/codecs.c
+Py_hexdigits
+ucnhash_CAPI # slight performance-only race
+
+# Python/dynload_shlib.c
+_PyImport_DynLoadFiletab
+
+# Python/fileutils.c
+_Py_open_cloexec_works
+force_ascii
+
+# Python/frozen.c
+M___hello__
+PyImport_FrozenModules
+
+# Python/graminit.c
+_PyParser_Grammar
+dfas
+labels
+
+# Python/import.c
+PyImport_Inittab
+
+# Python/pylifecycle.c
+_TARGET_LOCALES
+
+
+#---------------------------------
+# initialized (PyObject)
+
+# Objects/bytesobject.c
+characters
+nullstring
+
+# Objects/exceptions.c
+PyExc_RecursionErrorInst
+errnomap
+
+# Objects/longobject.c
+_PyLong_One
+_PyLong_Zero
+small_ints
+
+# Objects/setobject.c
+emptyfrozenset
+
+# Objects/unicodeobject.c
+interned # slight race on init in PyUnicode_InternInPlace()
+unicode_empty
+unicode_latin1
+
+
+#---------------------------------
+# initialized (other)
+
+# Python/getargs.c
+static_arg_parsers
+
+# Python/pyhash.c
+PyHash_Func
+_Py_HashSecret
+_Py_HashSecret_Initialized
+
+# Python/pylifecycle.c
+_Py_StandardStreamEncoding
+_Py_StandardStreamErrors
+default_home
+env_home
+progname
+Py_BytesWarningFlag
+Py_DebugFlag
+Py_DontWriteBytecodeFlag
+Py_FrozenFlag
+Py_HashRandomizationFlag
+Py_IgnoreEnvironmentFlag
+Py_InspectFlag
+Py_InteractiveFlag
+Py_IsolatedFlag
+Py_NoSiteFlag
+Py_NoUserSiteDirectory
+Py_OptimizeFlag
+Py_QuietFlag
+Py_UnbufferedStdioFlag
+Py_UseClassExceptionsFlag
+Py_VerboseFlag
+
+
+#---------------------------------
+# types
+
+# Modules/_threadmodule.c
+Locktype
+RLocktype
+localdummytype
+localtype
+
+# Objects/exceptions.c
+PyExc_BaseException
+PyExc_Exception
+PyExc_GeneratorExit
+PyExc_KeyboardInterrupt
+PyExc_StopAsyncIteration
+PyExc_StopIteration
+PyExc_SystemExit
+_PyExc_BaseException
+_PyExc_Exception
+_PyExc_GeneratorExit
+_PyExc_KeyboardInterrupt
+_PyExc_StopAsyncIteration
+_PyExc_StopIteration
+_PyExc_SystemExit
+
+# Objects/structseq.c
+_struct_sequence_template
+
+
+#---------------------------------
+# interned strings/bytes
+
+# Modules/_io/_iomodule.c
+_PyIO_empty_bytes
+_PyIO_empty_str
+_PyIO_str_close
+_PyIO_str_closed
+_PyIO_str_decode
+_PyIO_str_encode
+_PyIO_str_fileno
+_PyIO_str_flush
+_PyIO_str_getstate
+_PyIO_str_isatty
+_PyIO_str_newlines
+_PyIO_str_nl
+_PyIO_str_read
+_PyIO_str_read1
+_PyIO_str_readable
+_PyIO_str_readall
+_PyIO_str_readinto
+_PyIO_str_readline
+_PyIO_str_reset
+_PyIO_str_seek
+_PyIO_str_seekable
+_PyIO_str_setstate
+_PyIO_str_tell
+_PyIO_str_truncate
+_PyIO_str_writable
+_PyIO_str_write
+
+# Modules/_threadmodule.c
+str_dict
+
+# Objects/boolobject.c
+false_str
+true_str
+
+# Objects/listobject.c
+indexerr
+
+# Python/symtable.c
+__class__
+dictcomp
+genexpr
+lambda
+listcomp
+setcomp
+top
+
+# Python/sysmodule.c
+whatstrings
+
+
+#######################################
+# hacks
+
+# Objects/object.c
+_Py_abstract_hack
+
+# Objects/setobject.c
+_PySet_Dummy
+
+# Python/pylifecycle.c
+_PyOS_mystrnicmp_hack