diff options
author | Eric Snow <ericsnowcurrently@gmail.com> | 2020-10-22 18:42:51 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-22 18:42:51 -0600 |
commit | 345cd37abe324ad4f60f80e2c3133b8849e54e9b (patch) | |
tree | 5d965e662dca9dcac19e7eddd63a3d9d0b816fed /Tools | |
parent | bpo-38486: Fix dead qmail links in the mailbox docs (GH-22239) (diff) | |
download | cpython-345cd37abe324ad4f60f80e2c3133b8849e54e9b.tar.gz cpython-345cd37abe324ad4f60f80e2c3133b8849e54e9b.tar.bz2 cpython-345cd37abe324ad4f60f80e2c3133b8849e54e9b.zip |
bpo-36876: Fix the C analyzer tool. (GH-22841)
The original tool wasn't working right and it was simpler to create a new one, partially re-using some of the old code. At this point the tool runs properly on the master. (Try: ./python Tools/c-analyzer/c-analyzer.py analyze.) It take ~40 seconds on my machine to analyze the full CPython code base.
Note that we'll need to iron out some OS-specific stuff (e.g. preprocessor). We're okay though since this tool isn't used yet in our workflow. We will also need to verify the analysis results in detail before activating the check in CI, though I'm pretty sure it's close.
https://bugs.python.org/issue36876
Diffstat (limited to 'Tools')
72 files changed, 8882 insertions, 6239 deletions
diff --git a/Tools/c-analyzer/README b/Tools/c-analyzer/README index 8cf20e276d9..86bf1e77e0b 100644 --- a/Tools/c-analyzer/README +++ b/Tools/c-analyzer/README @@ -36,6 +36,10 @@ should be run to ensure that no new globals have been added: python3 Tools/c-analyzer/check-c-globals.py +You can also use the more generic tool: + + python3 Tools/c-analyzer/c-analyzer.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-analyzer/c-analyzer.py b/Tools/c-analyzer/c-analyzer.py new file mode 100644 index 00000000000..4a5e88cdaf1 --- /dev/null +++ b/Tools/c-analyzer/c-analyzer.py @@ -0,0 +1,7 @@ +from cpython.__main__ import parse_args, main, configure_logger + + +cmd, cmd_kwargs, verbosity, traceback_cm = parse_args() +configure_logger(verbosity) +with traceback_cm: + main(cmd, cmd_kwargs) diff --git a/Tools/c-analyzer/c-globals.py b/Tools/c-analyzer/c-globals.py deleted file mode 100644 index b36b791241d..00000000000 --- a/Tools/c-analyzer/c-globals.py +++ /dev/null @@ -1,9 +0,0 @@ -# This is a script equivalent of running "python -m test.test_c_globals.cg". - -from cpython.__main__ import parse_args, main - - -# This is effectively copied from cg/__main__.py: -if __name__ == '__main__': - cmd, cmdkwargs = parse_args() - main(cmd, cmdkwargs) diff --git a/Tools/c-analyzer/c_analyzer/__init__.py b/Tools/c-analyzer/c_analyzer/__init__.py index e69de29bb2d..4a01cd396f5 100644 --- a/Tools/c-analyzer/c_analyzer/__init__.py +++ b/Tools/c-analyzer/c_analyzer/__init__.py @@ -0,0 +1,103 @@ +from c_parser import ( + parse_files as _parse_files, +) +from c_parser.info import ( + KIND, + TypeDeclaration, + filter_by_kind, + collate_by_kind_group, + resolve_parsed, +) +from . import ( + analyze as _analyze, + datafiles as _datafiles, +) +from .info import Analysis + + +def analyze(filenmes, **kwargs): + results = iter_analyis_results(filenames, **kwargs) + return Analysis.from_results(results) + + +def iter_analysis_results(filenmes, *, + known=None, + **kwargs + ): + decls = iter_decls(filenames, **kwargs) + yield from analyze_decls(decls, known) + + +def iter_decls(filenames, *, + kinds=None, + parse_files=_parse_files, + **kwargs + ): + kinds = KIND.DECLS if kinds is None else (KIND.DECLS & set(kinds)) + parse_files = parse_files or _parse_files + + parsed = parse_files(filenames, **kwargs) + parsed = filter_by_kind(parsed, kinds) + for item in parsed: + yield resolve_parsed(item) + + +def analyze_decls(decls, known, *, + analyze_resolved=None, + handle_unresolved=True, + relroot=None, + ): + knowntypes, knowntypespecs = _datafiles.get_known( + known, + handle_unresolved=handle_unresolved, + analyze_resolved=analyze_resolved, + relroot=relroot, + ) + + decls = list(decls) + collated = collate_by_kind_group(decls) + + types = {decl: None for decl in collated['type']} + typespecs = _analyze.get_typespecs(types) + + def analyze_decl(decl): + return _analyze.analyze_decl( + decl, + typespecs, + knowntypespecs, + types, + knowntypes, + analyze_resolved=analyze_resolved, + ) + _analyze.analyze_type_decls(types, analyze_decl, handle_unresolved) + for decl in decls: + if decl in types: + resolved = types[decl] + else: + resolved = analyze_decl(decl) + if resolved and handle_unresolved: + typedeps, _ = resolved + if not isinstance(typedeps, TypeDeclaration): + if not typedeps or None in typedeps: + raise NotImplementedError((decl, resolved)) + + yield decl, resolved + + +####################################### +# checks + +def check_all(analysis, checks, *, failfast=False): + for check in checks or (): + for data, failure in check(analysis): + if failure is None: + continue + + yield data, failure + if failfast: + yield None, None + break + else: + continue + # We failed fast. + break diff --git a/Tools/c-analyzer/c_analyzer/__main__.py b/Tools/c-analyzer/c_analyzer/__main__.py new file mode 100644 index 00000000000..1fd45b985d9 --- /dev/null +++ b/Tools/c-analyzer/c_analyzer/__main__.py @@ -0,0 +1,501 @@ +import io +import logging +import os.path +import re +import sys + +from c_common.logging import VERBOSITY, Printer +from c_common.scriptutil import ( + add_verbosity_cli, + add_traceback_cli, + add_sepval_cli, + add_files_cli, + add_commands_cli, + process_args_by_key, + configure_logger, + get_prog, + filter_filenames, + iter_marks, +) +from c_parser.info import KIND, is_type_decl +from . import ( + analyze as _analyze, + check_all as _check_all, + datafiles as _datafiles, +) + + +KINDS = [ + KIND.TYPEDEF, + KIND.STRUCT, + KIND.UNION, + KIND.ENUM, + KIND.FUNCTION, + KIND.VARIABLE, + KIND.STATEMENT, +] + +logger = logging.getLogger(__name__) + + +####################################### +# table helpers + +TABLE_SECTIONS = { + 'types': ( + ['kind', 'name', 'data', 'file'], + is_type_decl, + (lambda v: (v.kind.value, v.filename or '', v.name)), + ), + 'typedefs': 'types', + 'structs': 'types', + 'unions': 'types', + 'enums': 'types', + 'functions': ( + ['name', 'data', 'file'], + (lambda kind: kind is KIND.FUNCTION), + (lambda v: (v.filename or '', v.name)), + ), + 'variables': ( + ['name', 'parent', 'data', 'file'], + (lambda kind: kind is KIND.VARIABLE), + (lambda v: (v.filename or '', str(v.parent) if v.parent else '', v.name)), + ), + 'statements': ( + ['file', 'parent', 'data'], + (lambda kind: kind is KIND.STATEMENT), + (lambda v: (v.filename or '', str(v.parent) if v.parent else '', v.name)), + ), + KIND.TYPEDEF: 'typedefs', + KIND.STRUCT: 'structs', + KIND.UNION: 'unions', + KIND.ENUM: 'enums', + KIND.FUNCTION: 'functions', + KIND.VARIABLE: 'variables', + KIND.STATEMENT: 'statements', +} + + +def _render_table(items, columns, relroot=None): + # XXX improve this + header = '\t'.join(columns) + div = '--------------------' + yield header + yield div + total = 0 + for item in items: + rowdata = item.render_rowdata(columns) + row = [rowdata[c] for c in columns] + if relroot and 'file' in columns: + index = columns.index('file') + row[index] = os.path.relpath(row[index], relroot) + yield '\t'.join(row) + total += 1 + yield div + yield f'total: {total}' + + +def build_section(name, groupitems, *, relroot=None): + info = TABLE_SECTIONS[name] + while type(info) is not tuple: + if name in KINDS: + name = info + info = TABLE_SECTIONS[info] + + columns, match_kind, sortkey = info + items = (v for v in groupitems if match_kind(v.kind)) + items = sorted(items, key=sortkey) + def render(): + yield '' + yield f'{name}:' + yield '' + for line in _render_table(items, columns, relroot): + yield line + return items, render + + +####################################### +# the checks + +CHECKS = { + #'globals': _check_globals, +} + + +def add_checks_cli(parser, checks=None, *, add_flags=None): + default = False + if not checks: + checks = list(CHECKS) + default = True + elif isinstance(checks, str): + checks = [checks] + if (add_flags is None and len(checks) > 1) or default: + add_flags = True + + process_checks = add_sepval_cli(parser, '--check', 'checks', checks) + if add_flags: + for check in checks: + parser.add_argument(f'--{check}', dest='checks', + action='append_const', const=check) + return [ + process_checks, + ] + + +def _get_check_handlers(fmt, printer, verbosity=VERBOSITY): + div = None + def handle_after(): + pass + if not fmt: + div = '' + def handle_failure(failure, data): + data = repr(data) + if verbosity >= 3: + logger.info(f'failure: {failure}') + logger.info(f'data: {data}') + else: + logger.warn(f'failure: {failure} (data: {data})') + elif fmt == 'raw': + def handle_failure(failure, data): + print(f'{failure!r} {data!r}') + elif fmt == 'brief': + def handle_failure(failure, data): + parent = data.parent or '' + funcname = parent if isinstance(parent, str) else parent.name + name = f'({funcname}).{data.name}' if funcname else data.name + failure = failure.split('\t')[0] + print(f'{data.filename}:{name} - {failure}') + elif fmt == 'summary': + def handle_failure(failure, data): + parent = data.parent or '' + funcname = parent if isinstance(parent, str) else parent.name + print(f'{data.filename:35}\t{funcname or "-":35}\t{data.name:40}\t{failure}') + elif fmt == 'full': + div = '' + def handle_failure(failure, data): + name = data.shortkey if data.kind is KIND.VARIABLE else data.name + parent = data.parent or '' + funcname = parent if isinstance(parent, str) else parent.name + known = 'yes' if data.is_known else '*** NO ***' + print(f'{data.kind.value} {name!r} failed ({failure})') + print(f' file: {data.filename}') + print(f' func: {funcname or "-"}') + print(f' name: {data.name}') + print(f' data: ...') + print(f' type unknown: {known}') + else: + if fmt in FORMATS: + raise NotImplementedError(fmt) + raise ValueError(f'unsupported fmt {fmt!r}') + return handle_failure, handle_after, div + + +####################################### +# the formats + +def fmt_raw(analysis): + for item in analysis: + yield from item.render('raw') + + +def fmt_brief(analysis): + # XXX Support sorting. + items = sorted(analysis) + for kind in KINDS: + if kind is KIND.STATEMENT: + continue + for item in items: + if item.kind is not kind: + continue + yield from item.render('brief') + yield f' total: {len(items)}' + + +def fmt_summary(analysis): + # XXX Support sorting and grouping. + items = list(analysis) + total = len(items) + + def section(name): + _, render = build_section(name, items) + yield from render() + + yield from section('types') + yield from section('functions') + yield from section('variables') + yield from section('statements') + + yield '' +# yield f'grand total: {len(supported) + len(unsupported)}' + yield f'grand total: {total}' + + +def fmt_full(analysis): + # XXX Support sorting. + items = sorted(analysis, key=lambda v: v.key) + yield '' + for item in items: + yield from item.render('full') + yield '' + yield f'total: {len(items)}' + + +FORMATS = { + 'raw': fmt_raw, + 'brief': fmt_brief, + 'summary': fmt_summary, + 'full': fmt_full, +} + + +def add_output_cli(parser, *, default='summary'): + parser.add_argument('--format', dest='fmt', default=default, choices=tuple(FORMATS)) + + def process_args(args): + pass + return process_args + + +####################################### +# the commands + +def _cli_check(parser, checks=None, **kwargs): + if isinstance(checks, str): + checks = [checks] + if checks is False: + process_checks = None + elif checks is None: + process_checks = add_checks_cli(parser) + elif len(checks) == 1 and type(checks) is not dict and re.match(r'^<.*>$', checks[0]): + check = checks[0][1:-1] + def process_checks(args): + args.checks = [check] + else: + process_checks = add_checks_cli(parser, checks=checks) + process_output = add_output_cli(parser, default=None) + process_files = add_files_cli(parser, **kwargs) + return [ + process_checks, + process_output, + process_files, + ] + + +def cmd_check(filenames, *, + checks=None, + ignored=None, + fmt=None, + relroot=None, + failfast=False, + iter_filenames=None, + verbosity=VERBOSITY, + _analyze=_analyze, + _CHECKS=CHECKS, + **kwargs + ): + if not checks: + checks = _CHECKS + elif isinstance(checks, str): + checks = [checks] + checks = [_CHECKS[c] if isinstance(c, str) else c + for c in checks] + printer = Printer(verbosity) + (handle_failure, handle_after, div + ) = _get_check_handlers(fmt, printer, verbosity) + + filenames = filter_filenames(filenames, iter_filenames) + + logger.info('analyzing...') + analyzed = _analyze(filenames, **kwargs) + if relroot: + analyzed.fix_filenames(relroot) + + logger.info('checking...') + numfailed = 0 + for data, failure in _check_all(analyzed, checks, failfast=failfast): + if data is None: + printer.info('stopping after one failure') + break + if div is not None and numfailed > 0: + printer.info(div) + numfailed += 1 + handle_failure(failure, data) + handle_after() + + printer.info('-------------------------') + logger.info(f'total failures: {numfailed}') + logger.info('done checking') + + if numfailed > 0: + sys.exit(numfailed) + + +def _cli_analyze(parser, **kwargs): + process_output = add_output_cli(parser) + process_files = add_files_cli(parser, **kwargs) + return [ + process_output, + process_files, + ] + + +# XXX Support filtering by kind. +def cmd_analyze(filenames, *, + fmt=None, + iter_filenames=None, + verbosity=None, + _analyze=_analyze, + formats=FORMATS, + **kwargs + ): + verbosity = verbosity if verbosity is not None else 3 + + try: + do_fmt = formats[fmt] + except KeyError: + raise ValueError(f'unsupported fmt {fmt!r}') + + filenames = filter_filenames(filenames, iter_filenames) + if verbosity == 2: + def iter_filenames(filenames=filenames): + marks = iter_marks() + for filename in filenames: + print(next(marks), end='') + yield filename + filenames = iter_filenames() + elif verbosity > 2: + def iter_filenames(filenames=filenames): + for filename in filenames: + print(f'<{filename}>') + yield filename + filenames = iter_filenames() + + logger.info('analyzing...') + analyzed = _analyze(filenames, **kwargs) + + for line in do_fmt(analyzed): + print(line) + + +def _cli_data(parser, filenames=None, known=None): + ArgumentParser = type(parser) + common = ArgumentParser(add_help=False) + if filenames is None: + common.add_argument('filenames', metavar='FILE', nargs='+') + + subs = parser.add_subparsers(dest='datacmd') + + sub = subs.add_parser('show', parents=[common]) + if known is None: + sub.add_argument('--known', required=True) + + sub = subs.add_parser('dump') + if known is None: + sub.add_argument('--known') + sub.add_argument('--show', action='store_true') + + sub = subs.add_parser('check') + if known is None: + sub.add_argument('--known', required=True) + + return None + + +def cmd_data(datacmd, filenames, known=None, *, + _analyze=_analyze, + formats=FORMATS, + extracolumns=None, + relroot=None, + **kwargs + ): + kwargs.pop('verbosity', None) + usestdout = kwargs.pop('show', None) + if datacmd == 'show': + do_fmt = formats['summary'] + if isinstance(known, str): + known, _ = _datafiles.get_known(known, extracolumns, relroot) + for line in do_fmt(known): + print(line) + elif datacmd == 'dump': + analyzed = _analyze(filenames, **kwargs) + if known is None or usestdout: + outfile = io.StringIO() + _datafiles.write_known(analyzed, outfile, extracolumns, + relroot=relroot) + print(outfile.getvalue()) + else: + _datafiles.write_known(analyzed, known, extracolumns, + relroot=relroot) + elif datacmd == 'check': + raise NotImplementedError(datacmd) + else: + raise ValueError(f'unsupported data command {datacmd!r}') + + +COMMANDS = { + 'check': ( + 'analyze and fail if the given C source/header files have any problems', + [_cli_check], + cmd_check, + ), + 'analyze': ( + 'report on the state of the given C source/header files', + [_cli_analyze], + cmd_analyze, + ), + 'data': ( + 'check/manage local data (e.g. knwon types, ignored vars, caches)', + [_cli_data], + cmd_data, + ), +} + + +####################################### +# the script + +def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *, subset=None): + import argparse + parser = argparse.ArgumentParser( + prog=prog or get_prog(), + ) + + processors = add_commands_cli( + parser, + commands={k: v[1] for k, v in COMMANDS.items()}, + commonspecs=[ + add_verbosity_cli, + add_traceback_cli, + ], + subset=subset, + ) + + args = parser.parse_args(argv) + ns = vars(args) + + cmd = ns.pop('cmd') + + verbosity, traceback_cm = process_args_by_key( + args, + processors[cmd], + ['verbosity', 'traceback_cm'], + ) + # "verbosity" is sent to the commands, so we put it back. + args.verbosity = verbosity + + return cmd, ns, verbosity, traceback_cm + + +def main(cmd, cmd_kwargs): + try: + run_cmd = COMMANDS[cmd][0] + except KeyError: + raise ValueError(f'unsupported cmd {cmd!r}') + run_cmd(**cmd_kwargs) + + +if __name__ == '__main__': + cmd, cmd_kwargs, verbosity, traceback_cm = parse_args() + configure_logger(verbosity) + with traceback_cm: + main(cmd, cmd_kwargs) diff --git a/Tools/c-analyzer/c_analyzer/analyze.py b/Tools/c-analyzer/c_analyzer/analyze.py new file mode 100644 index 00000000000..d8ae915e420 --- /dev/null +++ b/Tools/c-analyzer/c_analyzer/analyze.py @@ -0,0 +1,307 @@ +from c_parser.info import ( + KIND, + TypeDeclaration, + POTSType, + FuncPtr, + is_pots, + is_funcptr, +) +from .info import ( + IGNORED, + UNKNOWN, + is_system_type, + SystemType, +) + + +def get_typespecs(typedecls): + typespecs = {} + for decl in typedecls: + if decl.shortkey not in typespecs: + typespecs[decl.shortkey] = [decl] + else: + typespecs[decl.shortkey].append(decl) + return typespecs + + +def analyze_decl(decl, typespecs, knowntypespecs, types, knowntypes, *, + analyze_resolved=None): + resolved = resolve_decl(decl, typespecs, knowntypespecs, types) + if resolved is None: + # The decl is supposed to be skipped or ignored. + return None + if analyze_resolved is None: + return resolved, None + return analyze_resolved(resolved, decl, types, knowntypes) + +# This alias helps us avoid name collisions. +_analyze_decl = analyze_decl + + +def analyze_type_decls(types, analyze_decl, handle_unresolved=True): + unresolved = set(types) + while unresolved: + updated = [] + for decl in unresolved: + resolved = analyze_decl(decl) + if resolved is None: + # The decl should be skipped or ignored. + types[decl] = IGNORED + updated.append(decl) + continue + typedeps, _ = resolved + if typedeps is None: + raise NotImplementedError(decl) + if UNKNOWN in typedeps: + # At least one dependency is unknown, so this decl + # is not resolvable. + types[decl] = UNKNOWN + updated.append(decl) + continue + if None in typedeps: + # XXX + # Handle direct recursive types first. + nonrecursive = 1 + if decl.kind is KIND.STRUCT or decl.kind is KIND.UNION: + nonrecursive = 0 + i = 0 + for member, dep in zip(decl.members, typedeps): + if dep is None: + if member.vartype.typespec != decl.shortkey: + nonrecursive += 1 + else: + typedeps[i] = decl + i += 1 + if nonrecursive: + # We don't have all dependencies resolved yet. + continue + types[decl] = resolved + updated.append(decl) + if updated: + for decl in updated: + unresolved.remove(decl) + else: + # XXX + # Handle indirect recursive types. + ... + # We couldn't resolve the rest. + # Let the caller deal with it! + break + if unresolved and handle_unresolved: + if handle_unresolved is True: + handle_unresolved = _handle_unresolved + handle_unresolved(unresolved, types, analyze_decl) + + +def resolve_decl(decl, typespecs, knowntypespecs, types): + if decl.kind is KIND.ENUM: + typedeps = [] + else: + if decl.kind is KIND.VARIABLE: + vartypes = [decl.vartype] + elif decl.kind is KIND.FUNCTION: + vartypes = [decl.signature.returntype] + elif decl.kind is KIND.TYPEDEF: + vartypes = [decl.vartype] + elif decl.kind is KIND.STRUCT or decl.kind is KIND.UNION: + vartypes = [m.vartype for m in decl.members] + else: + # Skip this one! + return None + + typedeps = [] + for vartype in vartypes: + typespec = vartype.typespec + if is_pots(typespec): + typedecl = POTSType(typespec) + elif is_system_type(typespec): + typedecl = SystemType(typespec) + elif is_funcptr(vartype): + typedecl = FuncPtr(vartype) + else: + typedecl = find_typedecl(decl, typespec, typespecs) + if typedecl is None: + typedecl = find_typedecl(decl, typespec, knowntypespecs) + elif not isinstance(typedecl, TypeDeclaration): + raise NotImplementedError(repr(typedecl)) + if typedecl is None: + # We couldn't find it! + typedecl = UNKNOWN + elif typedecl not in types: + # XXX How can this happen? + typedecl = UNKNOWN + elif types[typedecl] is UNKNOWN: + typedecl = UNKNOWN + elif types[typedecl] is IGNORED: + # We don't care if it didn't resolve. + pass + elif types[typedecl] is None: + # The typedecl for the typespec hasn't been resolved yet. + typedecl = None + typedeps.append(typedecl) + return typedeps + + +def find_typedecl(decl, typespec, typespecs): + specdecls = typespecs.get(typespec) + if not specdecls: + return None + + filename = decl.filename + + if len(specdecls) == 1: + typedecl, = specdecls + if '-' in typespec and typedecl.filename != filename: + # Inlined types are always in the same file. + return None + return typedecl + + # Decide which one to return. + candidates = [] + samefile = None + for typedecl in specdecls: + type_filename = typedecl.filename + if type_filename == filename: + if samefile is not None: + # We expect type names to be unique in a file. + raise NotImplementedError((decl, samefile, typedecl)) + samefile = typedecl + elif filename.endswith('.c') and not type_filename.endswith('.h'): + # If the decl is in a source file then we expect the + # type to be in the same file or in a header file. + continue + candidates.append(typedecl) + if not candidates: + return None + elif len(candidates) == 1: + winner, = candidates + # XXX Check for inline? + elif '-' in typespec: + # Inlined types are always in the same file. + winner = samefile + elif samefile is not None: + # Favor types in the same file. + winner = samefile + else: + # We don't know which to return. + raise NotImplementedError((decl, candidates)) + + return winner + + +############################# +# handling unresolved decls + +class Skipped(TypeDeclaration): + def __init__(self): + _file = _name = _data = _parent = None + super().__init__(_file, _name, _data, _parent, _shortkey='<skipped>') +_SKIPPED = Skipped() +del Skipped + + +def _handle_unresolved(unresolved, types, analyze_decl): + #raise NotImplementedError(unresolved) + + dump = True + dump = False + if dump: + print() + for decl in types: # Preserve the original order. + if decl not in unresolved: + assert types[decl] is not None, decl + if types[decl] in (UNKNOWN, IGNORED): + unresolved.add(decl) + if dump: + _dump_unresolved(decl, types, analyze_decl) + print() + else: + assert types[decl][0] is not None, (decl, types[decl]) + assert None not in types[decl][0], (decl, types[decl]) + else: + assert types[decl] is None + if dump: + _dump_unresolved(decl, types, analyze_decl) + print() + #raise NotImplementedError + + for decl in unresolved: + types[decl] = ([_SKIPPED], None) + + for decl in types: + assert types[decl] + + +def _dump_unresolved(decl, types, analyze_decl): + if isinstance(decl, str): + typespec = decl + decl, = (d for d in types if d.shortkey == typespec) + elif type(decl) is tuple: + filename, typespec = decl + if '-' in typespec: + found = [d for d in types + if d.shortkey == typespec and d.filename == filename] + #if not found: + # raise NotImplementedError(decl) + decl, = found + else: + found = [d for d in types if d.shortkey == typespec] + if not found: + print(f'*** {typespec} ???') + return + #raise NotImplementedError(decl) + else: + decl, = found + resolved = analyze_decl(decl) + if resolved: + typedeps, _ = resolved or (None, None) + + if decl.kind is KIND.STRUCT or decl.kind is KIND.UNION: + print(f'*** {decl.shortkey} {decl.filename}') + for member, mtype in zip(decl.members, typedeps): + typespec = member.vartype.typespec + if typespec == decl.shortkey: + print(f' ~~~~: {typespec:20} - {member!r}') + continue + status = None + if is_pots(typespec): + mtype = typespec + status = 'okay' + elif is_system_type(typespec): + mtype = typespec + status = 'okay' + elif mtype is None: + if '-' in member.vartype.typespec: + mtype, = [d for d in types + if d.shortkey == member.vartype.typespec + and d.filename == decl.filename] + else: + found = [d for d in types + if d.shortkey == typespec] + if not found: + print(f' ???: {typespec:20}') + continue + mtype, = found + if status is None: + status = 'okay' if types.get(mtype) else 'oops' + if mtype is _SKIPPED: + status = 'okay' + mtype = '<skipped>' + elif isinstance(mtype, FuncPtr): + status = 'okay' + mtype = str(mtype.vartype) + elif not isinstance(mtype, str): + if hasattr(mtype, 'vartype'): + if is_funcptr(mtype.vartype): + status = 'okay' + mtype = str(mtype).rpartition('(')[0].rstrip() + status = ' okay' if status == 'okay' else f'--> {status}' + print(f' {status}: {typespec:20} - {member!r} ({mtype})') + else: + print(f'*** {decl} ({decl.vartype!r})') + if decl.vartype.typespec.startswith('struct ') or is_funcptr(decl): + _dump_unresolved( + (decl.filename, decl.vartype.typespec), + types, + analyze_decl, + ) diff --git a/Tools/c-analyzer/c_analyzer/common/files.py b/Tools/c-analyzer/c_analyzer/common/files.py deleted file mode 100644 index a8a044757d0..00000000000 --- a/Tools/c-analyzer/c_analyzer/common/files.py +++ /dev/null @@ -1,124 +0,0 @@ -import glob -import os -import os.path - -# XXX need tests: -# * walk_tree() -# * glob_tree() -# * iter_files_by_suffix() - - -C_SOURCE_SUFFIXES = ('.c', '.h') - - -def _walk_tree(root, *, - _walk=os.walk, - ): - # A wrapper around os.walk that resolves the filenames. - for parent, _, names in _walk(root): - for name in names: - yield os.path.join(parent, name) - - -def walk_tree(root, *, - suffix=None, - walk=_walk_tree, - ): - """Yield each file in the tree under the given directory name. - - If "suffix" is provided then only files with that suffix will - be included. - """ - if suffix and not isinstance(suffix, str): - raise ValueError('suffix must be a string') - - for filename in walk(root): - if suffix and not filename.endswith(suffix): - continue - yield filename - - -def glob_tree(root, *, - suffix=None, - _glob=glob.iglob, - _escape=glob.escape, - _join=os.path.join, - ): - """Yield each file in the tree under the given directory name. - - If "suffix" is provided then only files with that suffix will - be included. - """ - suffix = suffix or '' - if not isinstance(suffix, str): - raise ValueError('suffix must be a string') - - for filename in _glob(_join(_escape(root), f'*{suffix}')): - yield filename - for filename in _glob(_join(_escape(root), f'**/*{suffix}')): - yield filename - - -def iter_files(root, suffix=None, relparent=None, *, - get_files=None, - _glob=glob_tree, - _walk=walk_tree, - ): - """Yield each file in the tree under the given directory name. - - If "root" is a non-string iterable then do the same for each of - those trees. - - If "suffix" is provided then only files with that suffix will - be included. - - if "relparent" is provided then it is used to resolve each - filename as a relative path. - """ - if get_files is None: - get_files = os.walk - if not isinstance(root, str): - roots = root - for root in roots: - yield from iter_files(root, suffix, relparent, - get_files=get_files, - _glob=_glob, _walk=_walk) - return - - # Use the right "walk" function. - if get_files in (glob.glob, glob.iglob, glob_tree): - get_files = _glob - else: - _files = _walk_tree if get_files in (os.walk, walk_tree) else get_files - get_files = (lambda *a, **k: _walk(*a, walk=_files, **k)) - - # Handle a single suffix. - if suffix and not isinstance(suffix, str): - filenames = get_files(root) - suffix = tuple(suffix) - else: - filenames = get_files(root, suffix=suffix) - suffix = None - - for filename in filenames: - if suffix and not isinstance(suffix, str): # multiple suffixes - if not filename.endswith(suffix): - continue - if relparent: - filename = os.path.relpath(filename, relparent) - yield filename - - -def iter_files_by_suffix(root, suffixes, relparent=None, *, - walk=walk_tree, - _iter_files=iter_files, - ): - """Yield each file in the tree that has the given suffixes. - - Unlike iter_files(), the results are in the original suffix order. - """ - if isinstance(suffixes, str): - suffixes = [suffixes] - # XXX Ignore repeated suffixes? - for suffix in suffixes: - yield from _iter_files(root, suffix, relparent) diff --git a/Tools/c-analyzer/c_analyzer/common/info.py b/Tools/c-analyzer/c_analyzer/common/info.py deleted file mode 100644 index 1a853a42ff2..00000000000 --- a/Tools/c-analyzer/c_analyzer/common/info.py +++ /dev/null @@ -1,138 +0,0 @@ -from collections import namedtuple -import re - -from .util import classonly, _NTBase - -# XXX need tests: -# * ID.match() - - -UNKNOWN = '???' - -# Does not start with digit and contains at least one letter. -NAME_RE = re.compile(r'(?!\d)(?=.*?[A-Za-z])\w+', re.ASCII) - - -class ID(_NTBase, namedtuple('ID', 'filename funcname name')): - """A unique ID for a single symbol or declaration.""" - - __slots__ = () - # XXX Add optional conditions (tuple of strings) field. - #conditions = Slot() - - @classonly - def from_raw(cls, raw): - if not raw: - return None - if isinstance(raw, str): - return cls(None, None, raw) - try: - name, = raw - filename = None - except ValueError: - try: - filename, name = raw - except ValueError: - return super().from_raw(raw) - return cls(filename, None, name) - - def __new__(cls, filename, funcname, name): - self = super().__new__( - cls, - filename=str(filename) if filename else None, - funcname=str(funcname) if funcname else None, - name=str(name) if name else None, - ) - #cls.conditions.set(self, tuple(str(s) if s else None - # for s in conditions or ())) - return self - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - if not self.name: - raise TypeError('missing name') - if not NAME_RE.fullmatch(self.name): - raise ValueError( - f'name must be an identifier, got {self.name!r}') - - # Symbols from a binary might not have filename/funcname info. - - if self.funcname: - if not self.filename: - raise TypeError('missing filename') - if not NAME_RE.fullmatch(self.funcname) and self.funcname != UNKNOWN: - raise ValueError( - f'name must be an identifier, got {self.funcname!r}') - - # XXX Require the filename (at least UNKONWN)? - # XXX Check the filename? - - @property - def islocal(self): - return self.funcname is not None - - def match(self, other, *, - match_files=(lambda f1, f2: f1 == f2), - ): - """Return True if the two match. - - At least one of the two must be completely valid (no UNKNOWN - anywhere). Otherwise False is returned. The remaining one - *may* have UNKNOWN for both funcname and filename. It must - have a valid name though. - - The caller is responsible for knowing which of the two is valid - (and which to use if both are valid). - """ - # First check the name. - if self.name is None: - return False - if other.name != self.name: - return False - - # Then check the filename. - if self.filename is None: - return False - if other.filename is None: - return False - if self.filename == UNKNOWN: - # "other" must be the valid one. - if other.funcname == UNKNOWN: - return False - elif self.funcname != UNKNOWN: - # XXX Try matching funcname even though we don't - # know the filename? - raise NotImplementedError - else: - return True - elif other.filename == UNKNOWN: - # "self" must be the valid one. - if self.funcname == UNKNOWN: - return False - elif other.funcname != UNKNOWN: - # XXX Try matching funcname even though we don't - # know the filename? - raise NotImplementedError - else: - return True - elif not match_files(self.filename, other.filename): - return False - - # Finally, check the funcname. - if self.funcname == UNKNOWN: - # "other" must be the valid one. - if other.funcname == UNKNOWN: - return False - else: - return other.funcname is not None - elif other.funcname == UNKNOWN: - # "self" must be the valid one. - if self.funcname == UNKNOWN: - return False - else: - return self.funcname is not None - elif self.funcname == other.funcname: - # Both are valid. - return True - - return False diff --git a/Tools/c-analyzer/c_analyzer/common/show.py b/Tools/c-analyzer/c_analyzer/common/show.py deleted file mode 100644 index 5f3cb1c2fb0..00000000000 --- a/Tools/c-analyzer/c_analyzer/common/show.py +++ /dev/null @@ -1,11 +0,0 @@ - -def basic(variables, *, - _print=print): - """Print each row simply.""" - for var in variables: - if var.funcname: - line = f'{var.filename}:{var.funcname}():{var.name}' - else: - line = f'{var.filename}:{var.name}' - line = f'{line:<64} {var.vartype}' - _print(line) diff --git a/Tools/c-analyzer/c_analyzer/datafiles.py b/Tools/c-analyzer/c_analyzer/datafiles.py new file mode 100644 index 00000000000..0de438cce47 --- /dev/null +++ b/Tools/c-analyzer/c_analyzer/datafiles.py @@ -0,0 +1,109 @@ +import c_common.tables as _tables +import c_parser.info as _info +import c_parser.datafiles as _parser +from . import analyze as _analyze + + +############################# +# "known" decls + +EXTRA_COLUMNS = [ + #'typedecl', +] + + +def analyze_known(known, *, + analyze_resolved=None, + handle_unresolved=True, + ): + knowntypes = knowntypespecs = {} + collated = _info.collate_by_kind_group(known) + types = {decl: None for decl in collated['type']} + typespecs = _analyze.get_typespecs(types) + def analyze_decl(decl): + return _analyze.analyze_decl( + decl, + typespecs, + knowntypespecs, + types, + knowntypes, + analyze_resolved=analyze_resolved, + ) + _analyze.analyze_type_decls(types, analyze_decl, handle_unresolved) + return types, typespecs + + +def get_known(known, extracolumns=None, *, + analyze_resolved=None, + handle_unresolved=True, + relroot=None, + ): + if isinstance(known, str): + known = read_known(known, extracolumns, relroot) + return analyze_known( + known, + handle_unresolved=handle_unresolved, + analyze_resolved=analyze_resolved, + ) + + +def read_known(infile, extracolumns=None, relroot=None): + extracolumns = EXTRA_COLUMNS + ( + list(extracolumns) if extracolumns else [] + ) + known = {} + for decl, extra in _parser.iter_decls_tsv(infile, extracolumns, relroot): + known[decl] = extra + return known + + +def write_known(rows, outfile, extracolumns=None, *, + relroot=None, + backup=True, + ): + extracolumns = EXTRA_COLUMNS + ( + list(extracolumns) if extracolumns else [] + ) + _parser.write_decls_tsv( + rows, + outfile, + extracolumns, + relroot=relroot, + backup=backup, + ) + + +############################# +# ignored vars + +IGNORED_COLUMNS = [ + 'filename', + 'funcname', + 'name', + 'reason', +] +IGNORED_HEADER = '\t'.join(IGNORED_COLUMNS) + + +def read_ignored(infile): + return dict(_iter_ignored(infile)) + + +def _iter_ignored(infile): + for row in _tables.read_table(infile, IGNORED_HEADER, sep='\t'): + *varidinfo, reason = row + varid = _info.DeclID.from_row(varidinfo) + yield varid, reason + + +def write_ignored(variables, outfile): + raise NotImplementedError + reason = '???' + #if not isinstance(varid, DeclID): + # varid = getattr(varid, 'parsed', varid).id + _tables.write_table( + outfile, + IGNORED_HEADER, + sep='\t', + rows=(r.render_rowdata() + (reason,) for r in decls), + ) diff --git a/Tools/c-analyzer/c_analyzer/info.py b/Tools/c-analyzer/c_analyzer/info.py new file mode 100644 index 00000000000..23d77611a4c --- /dev/null +++ b/Tools/c-analyzer/c_analyzer/info.py @@ -0,0 +1,353 @@ +from collections import namedtuple + +from c_common.clsutil import classonly +import c_common.misc as _misc +from c_parser.info import ( + KIND, + HighlevelParsedItem, + Declaration, + TypeDeclaration, + is_type_decl, + is_process_global, +) + + +IGNORED = _misc.Labeled('IGNORED') +UNKNOWN = _misc.Labeled('UNKNOWN') + + +# XXX Use known.tsv for these? +SYSTEM_TYPES = { + 'int8_t', + 'uint8_t', + 'int16_t', + 'uint16_t', + 'int32_t', + 'uint32_t', + 'int64_t', + 'uint64_t', + 'size_t', + 'ssize_t', + 'intptr_t', + 'uintptr_t', + 'wchar_t', + '', + # OS-specific + 'pthread_cond_t', + 'pthread_mutex_t', + 'pthread_key_t', + 'atomic_int', + 'atomic_uintptr_t', + '', + # lib-specific + 'WINDOW', # curses + 'XML_LChar', + 'XML_Size', + 'XML_Parser', + 'enum XML_Error', + 'enum XML_Status', + '', +} + + +def is_system_type(typespec): + return typespec in SYSTEM_TYPES + + +class SystemType(TypeDeclaration): + + def __init__(self, name): + super().__init__(None, name, None, None, _shortkey=name) + + +class Analyzed: + _locked = False + + @classonly + def is_target(cls, raw): + if isinstance(raw, HighlevelParsedItem): + return True + else: + return False + + @classonly + def from_raw(cls, raw, **extra): + if isinstance(raw, cls): + if extra: + # XXX ? + raise NotImplementedError((raw, extra)) + #return cls(raw.item, raw.typedecl, **raw._extra, **extra) + else: + return info + elif cls.is_target(raw): + return cls(raw, **extra) + else: + raise NotImplementedError((raw, extra)) + + @classonly + def from_resolved(cls, item, resolved, **extra): + if isinstance(resolved, TypeDeclaration): + return cls(item, typedecl=resolved, **extra) + else: + typedeps, extra = cls._parse_raw_resolved(item, resolved, extra) + if item.kind is KIND.ENUM: + if typedeps: + raise NotImplementedError((item, resolved, extra)) + elif not typedeps: + raise NotImplementedError((item, resolved, extra)) + return cls(item, typedeps, **extra or {}) + + @classonly + def _parse_raw_resolved(cls, item, resolved, extra_extra): + if resolved in (UNKNOWN, IGNORED): + return resolved, None + try: + typedeps, extra = resolved + except (TypeError, ValueError): + typedeps = extra = None + if extra: + # The resolved data takes precedence. + extra = dict(extra_extra, **extra) + if isinstance(typedeps, TypeDeclaration): + return typedeps, extra + elif typedeps in (None, UNKNOWN): + # It is still effectively unresolved. + return UNKNOWN, extra + elif None in typedeps or UNKNOWN in typedeps: + # It is still effectively unresolved. + return typedeps, extra + elif any(not isinstance(td, TypeDeclaration) for td in typedeps): + raise NotImplementedError((item, typedeps, extra)) + return typedeps, extra + + def __init__(self, item, typedecl=None, **extra): + assert item is not None + self.item = item + if typedecl in (UNKNOWN, IGNORED): + pass + elif item.kind is KIND.STRUCT or item.kind is KIND.UNION: + if isinstance(typedecl, TypeDeclaration): + raise NotImplementedError(item, typedecl) + elif typedecl is None: + typedecl = UNKNOWN + else: + typedecl = [UNKNOWN if d is None else d for d in typedecl] + elif typedecl is None: + typedecl = UNKNOWN + elif typedecl and not isinstance(typedecl, TypeDeclaration): + # All the other decls have a single type decl. + typedecl, = typedecl + if typedecl is None: + typedecl = UNKNOWN + self.typedecl = typedecl + self._extra = extra + self._locked = True + + self._validate() + + def _validate(self): + item = self.item + extra = self._extra + # Check item. + if not isinstance(item, HighlevelParsedItem): + raise ValueError(f'"item" must be a high-level parsed item, got {item!r}') + # Check extra. + for key, value in extra.items(): + if key.startswith('_'): + raise ValueError(f'extra items starting with {"_"!r} not allowed, got {extra!r}') + if hasattr(item, key) and not callable(getattr(item, key)): + raise ValueError(f'extra cannot override item, got {value!r} for key {key!r}') + + def __repr__(self): + kwargs = [ + f'item={self.item!r}', + f'typedecl={self.typedecl!r}', + *(f'{k}={v!r}' for k, v in self._extra.items()) + ] + return f'{type(self).__name__}({", ".join(kwargs)})' + + def __str__(self): + try: + return self._str + except AttributeError: + self._str, = self.render('line') + return self._str + + def __hash__(self): + return hash(self.item) + + def __eq__(self, other): + if isinstance(other, Analyzed): + return self.item == other.item + elif isinstance(other, HighlevelParsedItem): + return self.item == other + elif type(other) is tuple: + return self.item == other + else: + return NotImplemented + + def __gt__(self, other): + if isinstance(other, Analyzed): + return self.item > other.item + elif isinstance(other, HighlevelParsedItem): + return self.item > other + elif type(other) is tuple: + return self.item > other + else: + return NotImplemented + + def __dir__(self): + names = set(super().__dir__()) + names.update(self._extra) + names.remove('_locked') + return sorted(names) + + def __getattr__(self, name): + if name.startswith('_'): + raise AttributeError(name) + # The item takes precedence over the extra data (except if callable). + try: + value = getattr(self.item, name) + if callable(value): + raise AttributeError(name) + except AttributeError: + try: + value = self._extra[name] + except KeyError: + pass + else: + # Speed things up the next time. + self.__dict__[name] = value + return value + raise # re-raise + else: + return value + + def __setattr__(self, name, value): + if self._locked and name != '_str': + raise AttributeError(f'readonly ({name})') + super().__setattr__(name, value) + + def __delattr__(self, name): + if self._locked: + raise AttributeError(f'readonly ({name})') + super().__delattr__(name) + + @property + def decl(self): + if not isinstance(self.item, Declaration): + raise AttributeError('decl') + return self.item + + @property + def signature(self): + # XXX vartype... + ... + + @property + def istype(self): + return is_type_decl(self.item.kind) + + @property + def is_known(self): + if self.typedecl in (UNKNOWN, IGNORED): + return False + elif isinstance(self.typedecl, TypeDeclaration): + return True + else: + return UNKNOWN not in self.typedecl + + def fix_filename(self, relroot): + self.item.fix_filename(relroot) + + def as_rowdata(self, columns=None): + # XXX finsih! + return self.item.as_rowdata(columns) + + def render_rowdata(self, columns=None): + # XXX finsih! + return self.item.render_rowdata(columns) + + def render(self, fmt='line', *, itemonly=False): + if fmt == 'raw': + yield repr(self) + return + rendered = self.item.render(fmt) + if itemonly or not self._extra: + yield from rendered + return + extra = self._render_extra(fmt) + if not extra: + yield from rendered + elif fmt in ('brief', 'line'): + rendered, = rendered + extra, = extra + yield f'{rendered}\t{extra}' + elif fmt == 'summary': + raise NotImplementedError(fmt) + elif fmt == 'full': + yield from rendered + for line in extra: + yield f'\t{line}' + else: + raise NotImplementedError(fmt) + + def _render_extra(self, fmt): + if fmt in ('brief', 'line'): + yield str(self._extra) + else: + raise NotImplementedError(fmt) + + +class Analysis: + + _item_class = Analyzed + + @classonly + def build_item(cls, info, resolved=None, **extra): + if resolved is None: + return cls._item_class.from_raw(info, **extra) + else: + return cls._item_class.from_resolved(info, resolved, **extra) + + @classmethod + def from_results(cls, results): + self = cls() + for info, resolved in results: + self._add_result(info, resolved) + return self + + def __init__(self, items=None): + self._analyzed = {type(self).build_item(item): None + for item in items or ()} + + def __repr__(self): + return f'{type(self).__name__}({list(self._analyzed.keys())})' + + def __iter__(self): + #yield from self.types + #yield from self.functions + #yield from self.variables + yield from self._analyzed + + def __len__(self): + return len(self._analyzed) + + def __getitem__(self, key): + if type(key) is int: + for i, val in enumerate(self._analyzed): + if i == key: + return val + else: + raise IndexError(key) + else: + return self._analyzed[key] + + def fix_filenames(self, relroot): + for item in self._analyzed: + item.fix_filename(relroot) + + def _add_result(self, info, resolved): + analyzed = type(self).build_item(info, resolved) + self._analyzed[analyzed] = None + return analyzed diff --git a/Tools/c-analyzer/c_analyzer/parser/declarations.py b/Tools/c-analyzer/c_analyzer/parser/declarations.py deleted file mode 100644 index f37072cccad..00000000000 --- a/Tools/c-analyzer/c_analyzer/parser/declarations.py +++ /dev/null @@ -1,339 +0,0 @@ -import re -import shlex -import subprocess - -from ..common.info import UNKNOWN - -from . import source - - -IDENTIFIER = r'(?:[a-zA-z]|_+[a-zA-Z0-9]\w*)' - -TYPE_QUAL = r'(?:const|volatile)' - -VAR_TYPE_SPEC = r'''(?: - void | - (?: - (?:(?:un)?signed\s+)? - (?: - char | - short | - int | - long | - long\s+int | - long\s+long - ) | - ) | - float | - double | - {IDENTIFIER} | - (?:struct|union)\s+{IDENTIFIER} - )''' - -POINTER = rf'''(?: - (?:\s+const)?\s*[*] - )''' - -#STRUCT = r'''(?: -# (?:struct|(struct\s+%s))\s*[{] -# [^}]* -# [}] -# )''' % (IDENTIFIER) -#UNION = r'''(?: -# (?:union|(union\s+%s))\s*[{] -# [^}]* -# [}] -# )''' % (IDENTIFIER) -#DECL_SPEC = rf'''(?: -# ({VAR_TYPE_SPEC}) | -# ({STRUCT}) | -# ({UNION}) -# )''' - -FUNC_START = rf'''(?: - (?: - (?: - extern | - static | - static\s+inline - )\s+ - )? - #(?:const\s+)? - {VAR_TYPE_SPEC} - )''' -#GLOBAL_VAR_START = rf'''(?: -# (?: -# (?: -# extern | -# static -# )\s+ -# )? -# (?: -# {TYPE_QUAL} -# (?:\s+{TYPE_QUAL})? -# )?\s+ -# {VAR_TYPE_SPEC} -# )''' -GLOBAL_DECL_START_RE = re.compile(rf''' - ^ - (?: - ({FUNC_START}) - ) - ''', re.VERBOSE) - -LOCAL_VAR_START = rf'''(?: - (?: - (?: - register | - static - )\s+ - )? - (?: - (?: - {TYPE_QUAL} - (?:\s+{TYPE_QUAL})? - )\s+ - )? - {VAR_TYPE_SPEC} - {POINTER}? - )''' -LOCAL_STMT_START_RE = re.compile(rf''' - ^ - (?: - ({LOCAL_VAR_START}) - ) - ''', re.VERBOSE) - - -def iter_global_declarations(lines): - """Yield (decl, body) for each global declaration in the given lines. - - For function definitions the header is reduced to one line and - the body is provided as-is. For other compound declarations (e.g. - struct) the entire declaration is reduced to one line and "body" - is None. Likewise for simple declarations (e.g. variables). - - Declarations inside function bodies are ignored, though their text - is provided in the function body. - """ - # XXX Bail out upon bogus syntax. - lines = source.iter_clean_lines(lines) - for line in lines: - if not GLOBAL_DECL_START_RE.match(line): - continue - # We only need functions here, since we only need locals for now. - if line.endswith(';'): - continue - if line.endswith('{') and '(' not in line: - continue - - # Capture the function. - # (assume no func is a one-liner) - decl = line - while '{' not in line: # assume no inline structs, etc. - try: - line = next(lines) - except StopIteration: - return - decl += ' ' + line - - body, end = _extract_block(lines) - if end is None: - return - assert end == '}' - yield (f'{decl}\n{body}\n{end}', body) - - -def iter_local_statements(lines): - """Yield (lines, blocks) for each statement in the given lines. - - For simple statements, "blocks" is None and the statement is reduced - to a single line. For compound statements, "blocks" is a pair of - (header, body) for each block in the statement. The headers are - reduced to a single line each, but the bpdies are provided as-is. - """ - # XXX Bail out upon bogus syntax. - lines = source.iter_clean_lines(lines) - for line in lines: - if not LOCAL_STMT_START_RE.match(line): - continue - - stmt = line - blocks = None - if not line.endswith(';'): - # XXX Support compound & multiline simple statements. - #blocks = [] - continue - - yield (stmt, blocks) - - -def _extract_block(lines): - end = None - depth = 1 - body = [] - for line in lines: - depth += line.count('{') - line.count('}') - if depth == 0: - end = line - break - body.append(line) - return '\n'.join(body), end - - -def parse_func(stmt, body): - """Return (name, signature) for the given function definition.""" - header, _, end = stmt.partition(body) - assert end.strip() == '}' - assert header.strip().endswith('{') - header, _, _= header.rpartition('{') - - signature = ' '.join(header.strip().splitlines()) - - _, _, name = signature.split('(')[0].strip().rpartition(' ') - assert name - - return name, signature - - -#TYPE_SPEC = rf'''(?: -# )''' -#VAR_DECLARATOR = rf'''(?: -# )''' -#VAR_DECL = rf'''(?: -# {TYPE_SPEC}+ -# {VAR_DECLARATOR} -# \s* -# )''' -#VAR_DECLARATION = rf'''(?: -# {VAR_DECL} -# (?: = [^=] [^;]* )? -# ; -# )''' -# -# -#def parse_variable(decl, *, inFunc=False): -# """Return [(name, storage, vartype)] for the given variable declaration.""" -# ... - - -def _parse_var(stmt): - """Return (name, vartype) for the given variable declaration.""" - stmt = stmt.rstrip(';') - m = LOCAL_STMT_START_RE.match(stmt) - assert m - vartype = m.group(0) - name = stmt[len(vartype):].partition('=')[0].strip() - - if name.startswith('('): - name, _, after = name[1:].partition(')') - assert after - name = name.replace('*', '* ') - inside, _, name = name.strip().rpartition(' ') - vartype = f'{vartype} ({inside.strip()}){after}' - else: - name = name.replace('*', '* ') - before, _, name = name.rpartition(' ') - vartype = f'{vartype} {before}' - - vartype = vartype.strip() - while ' ' in vartype: - vartype = vartype.replace(' ', ' ') - - return name, vartype - - -def extract_storage(decl, *, infunc=None): - """Return (storage, vartype) based on the given declaration. - - The default storage is "implicit" (or "local" if infunc is True). - """ - if decl == UNKNOWN: - return decl - if decl.startswith('static '): - return 'static' - #return 'static', decl.partition(' ')[2].strip() - elif decl.startswith('extern '): - return 'extern' - #return 'extern', decl.partition(' ')[2].strip() - elif re.match('.*\b(static|extern)\b', decl): - raise NotImplementedError - elif infunc: - return 'local' - else: - return 'implicit' - - -def parse_compound(stmt, blocks): - """Return (headers, bodies) for the given compound statement.""" - # XXX Identify declarations inside compound statements - # (if/switch/for/while). - raise NotImplementedError - - -def iter_variables(filename, *, - preprocessed=False, - _iter_source_lines=source.iter_lines, - _iter_global=iter_global_declarations, - _iter_local=iter_local_statements, - _parse_func=parse_func, - _parse_var=_parse_var, - _parse_compound=parse_compound, - ): - """Yield (funcname, name, vartype) for every variable in the given file.""" - if preprocessed: - raise NotImplementedError - lines = _iter_source_lines(filename) - for stmt, body in _iter_global(lines): - # At the file top-level we only have to worry about vars & funcs. - if not body: - name, vartype = _parse_var(stmt) - if name: - yield (None, name, vartype) - else: - funcname, _ = _parse_func(stmt, body) - localvars = _iter_locals(body, - _iter_statements=_iter_local, - _parse_var=_parse_var, - _parse_compound=_parse_compound, - ) - for name, vartype in localvars: - yield (funcname, name, vartype) - - -def _iter_locals(lines, *, - _iter_statements=iter_local_statements, - _parse_var=_parse_var, - _parse_compound=parse_compound, - ): - compound = [lines] - while compound: - body = compound.pop(0) - bodylines = body.splitlines() - for stmt, blocks in _iter_statements(bodylines): - if not blocks: - name, vartype = _parse_var(stmt) - if name: - yield (name, vartype) - else: - headers, bodies = _parse_compound(stmt, blocks) - for header in headers: - for line in header: - name, vartype = _parse_var(line) - if name: - yield (name, vartype) - compound.extend(bodies) - - -def iter_all(filename, *, - preprocessed=False, - ): - """Yield a Declaration for each one found. - - If there are duplicates, due to preprocessor conditionals, then - they are checked to make sure they are the same. - """ - # XXX For the moment we cheat. - for funcname, name, decl in iter_variables(filename, - preprocessed=preprocessed): - yield 'variable', funcname, name, decl diff --git a/Tools/c-analyzer/c_analyzer/parser/find.py b/Tools/c-analyzer/c_analyzer/parser/find.py deleted file mode 100644 index 3860d3d459b..00000000000 --- a/Tools/c-analyzer/c_analyzer/parser/find.py +++ /dev/null @@ -1,107 +0,0 @@ -from ..common.info import UNKNOWN, ID - -from . import declarations - -# XXX need tests: -# * variables -# * variable -# * variable_from_id - - -def _iter_vars(filenames, preprocessed, *, - handle_id=None, - _iter_decls=declarations.iter_all, - ): - if handle_id is None: - handle_id = ID - - for filename in filenames or (): - for kind, funcname, name, decl in _iter_decls(filename, - preprocessed=preprocessed, - ): - if kind != 'variable': - continue - varid = handle_id(filename, funcname, name) - yield varid, decl - - -# XXX Add a "handle_var" arg like we did for get_resolver()? - -def variables(*filenames, - perfilecache=None, - preprocessed=False, - known=None, # for types - handle_id=None, - _iter_vars=_iter_vars, - ): - """Yield (varid, decl) for each variable found in the given files. - - If "preprocessed" is provided (and not False/None) then it is used - to decide which tool to use to parse the source code after it runs - through the C preprocessor. Otherwise the raw - """ - if len(filenames) == 1 and not (filenames[0], str): - filenames, = filenames - - if perfilecache is None: - yield from _iter_vars(filenames, preprocessed) - else: - # XXX Cache per-file variables (e.g. `{filename: [(varid, decl)]}`). - raise NotImplementedError - - -def variable(name, filenames, *, - local=False, - perfilecache=None, - preprocessed=False, - handle_id=None, - _iter_vars=variables, - ): - """Return (varid, decl) for the first found variable that matches. - - If "local" is True then the first matching local variable in the - file will always be returned. To avoid that, pass perfilecache and - pop each variable from the cache after using it. - """ - for varid, decl in _iter_vars(filenames, - perfilecache=perfilecache, - preprocessed=preprocessed, - ): - if varid.name != name: - continue - if local: - if varid.funcname: - if varid.funcname == UNKNOWN: - raise NotImplementedError - return varid, decl - elif not varid.funcname: - return varid, decl - else: - return None, None # No matching variable was found. - - -def variable_from_id(id, filenames, *, - perfilecache=None, - preprocessed=False, - handle_id=None, - _get_var=variable, - ): - """Return (varid, decl) for the first found variable that matches.""" - local = False - if isinstance(id, str): - name = id - else: - if id.funcname == UNKNOWN: - local = True - elif id.funcname: - raise NotImplementedError - - name = id.name - if id.filename and id.filename != UNKNOWN: - filenames = [id.filename] - return _get_var(name, filenames, - local=local, - perfilecache=perfilecache, - preprocessed=preprocessed, - handle_id=handle_id, - ) diff --git a/Tools/c-analyzer/c_analyzer/parser/naive.py b/Tools/c-analyzer/c_analyzer/parser/naive.py deleted file mode 100644 index 4a4822d84ff..00000000000 --- a/Tools/c-analyzer/c_analyzer/parser/naive.py +++ /dev/null @@ -1,179 +0,0 @@ -import re - -from ..common.info import UNKNOWN, ID - -from .preprocessor import _iter_clean_lines - - -_NOT_SET = object() - - -def get_srclines(filename, *, - cache=None, - _open=open, - _iter_lines=_iter_clean_lines, - ): - """Return the file's lines as a list. - - Each line will have trailing whitespace removed (including newline). - - If a cache is given the it is used. - """ - if cache is not None: - try: - return cache[filename] - except KeyError: - pass - - with _open(filename) as srcfile: - srclines = [line - for _, line in _iter_lines(srcfile) - if not line.startswith('#')] - for i, line in enumerate(srclines): - srclines[i] = line.rstrip() - - if cache is not None: - cache[filename] = srclines - return srclines - - -def parse_variable_declaration(srcline): - """Return (name, decl) for the given declaration line.""" - # XXX possible false negatives... - decl, sep, _ = srcline.partition('=') - if not sep: - if not srcline.endswith(';'): - return None, None - decl = decl.strip(';') - decl = decl.strip() - m = re.match(r'.*\b(\w+)\s*(?:\[[^\]]*\])?$', decl) - if not m: - return None, None - name = m.group(1) - return name, decl - - -def parse_variable(srcline, funcname=None): - """Return (varid, decl) for the variable declared on the line (or None).""" - line = srcline.strip() - - # XXX Handle more than just static variables. - if line.startswith('static '): - if '(' in line and '[' not in line: - # a function - return None, None - return parse_variable_declaration(line) - else: - return None, None - - -def iter_variables(filename, *, - srccache=None, - parse_variable=None, - _get_srclines=get_srclines, - _default_parse_variable=parse_variable, - ): - """Yield (varid, decl) for each variable in the given source file.""" - if parse_variable is None: - parse_variable = _default_parse_variable - - indent = '' - prev = '' - funcname = None - for line in _get_srclines(filename, cache=srccache): - # remember current funcname - if funcname: - if line == indent + '}': - funcname = None - continue - else: - if '(' in prev and line == indent + '{': - if not prev.startswith('__attribute__'): - funcname = prev.split('(')[0].split()[-1] - prev = '' - continue - indent = line[:-len(line.lstrip())] - prev = line - - info = parse_variable(line, funcname) - if isinstance(info, list): - for name, _funcname, decl in info: - yield ID(filename, _funcname, name), decl - continue - name, decl = info - - if name is None: - continue - yield ID(filename, funcname, name), decl - - -def _match_varid(variable, name, funcname, ignored=None): - if ignored and variable in ignored: - return False - - if variable.name != name: - return False - - if funcname == UNKNOWN: - if not variable.funcname: - return False - elif variable.funcname != funcname: - return False - - return True - - -def find_variable(filename, funcname, name, *, - ignored=None, - srccache=None, # {filename: lines} - parse_variable=None, - _iter_variables=iter_variables, - ): - """Return the matching variable. - - Return None if the variable is not found. - """ - for varid, decl in _iter_variables(filename, - srccache=srccache, - parse_variable=parse_variable, - ): - if _match_varid(varid, name, funcname, ignored): - return varid, decl - else: - return None - - -def find_variables(varids, filenames=None, *, - srccache=_NOT_SET, - parse_variable=None, - _find_symbol=find_variable, - ): - """Yield (varid, decl) for each ID. - - If the variable is not found then its decl will be UNKNOWN. That - way there will be one resulting variable per given ID. - """ - if srccache is _NOT_SET: - srccache = {} - - used = set() - for varid in varids: - if varid.filename and varid.filename != UNKNOWN: - srcfiles = [varid.filename] - else: - if not filenames: - yield varid, UNKNOWN - continue - srcfiles = filenames - for filename in srcfiles: - varid, decl = _find_varid(filename, varid.funcname, varid.name, - ignored=used, - srccache=srccache, - parse_variable=parse_variable, - ) - if varid: - yield varid, decl - used.add(varid) - break - else: - yield varid, UNKNOWN diff --git a/Tools/c-analyzer/c_analyzer/parser/preprocessor.py b/Tools/c-analyzer/c_analyzer/parser/preprocessor.py deleted file mode 100644 index 41f306e5f80..00000000000 --- a/Tools/c-analyzer/c_analyzer/parser/preprocessor.py +++ /dev/null @@ -1,511 +0,0 @@ -from collections import namedtuple -import shlex -import os -import re - -from ..common import util, info - - -CONTINUATION = '\\' + os.linesep - -IDENTIFIER = r'(?:\w*[a-zA-Z]\w*)' -IDENTIFIER_RE = re.compile('^' + IDENTIFIER + '$') - - -def _coerce_str(value): - if not value: - return '' - return str(value).strip() - - -############################# -# directives - -DIRECTIVE_START = r''' - (?: - ^ \s* - [#] \s* - )''' -DIRECTIVE_TEXT = r''' - (?: - (?: \s+ ( .*\S ) )? - \s* $ - )''' -DIRECTIVE = rf''' - (?: - {DIRECTIVE_START} - ( - include | - error | warning | - pragma | - define | undef | - if | ifdef | ifndef | elseif | else | endif | - __FILE__ | __LINE__ | __DATE __ | __TIME__ | __TIMESTAMP__ - ) - {DIRECTIVE_TEXT} - )''' -# (?: -# [^\\\n] | -# \\ [^\n] | -# \\ \n -# )+ -# ) \n -# )''' -DIRECTIVE_RE = re.compile(DIRECTIVE, re.VERBOSE) - -DEFINE = rf''' - (?: - {DIRECTIVE_START} define \s+ - (?: - ( \w*[a-zA-Z]\w* ) - (?: \s* [(] ([^)]*) [)] )? - ) - {DIRECTIVE_TEXT} - )''' -DEFINE_RE = re.compile(DEFINE, re.VERBOSE) - - -def parse_directive(line): - """Return the appropriate directive for the given line.""" - line = line.strip() - if line.startswith('#'): - line = line[1:].lstrip() - line = '#' + line - directive = line - #directive = '#' + line - while ' ' in directive: - directive = directive.replace(' ', ' ') - return _parse_directive(directive) - - -def _parse_directive(line): - m = DEFINE_RE.match(line) - if m: - name, args, text = m.groups() - if args: - args = [a.strip() for a in args.split(',')] - return Macro(name, args, text) - else: - return Constant(name, text) - - m = DIRECTIVE_RE.match(line) - if not m: - raise ValueError(f'unsupported directive {line!r}') - kind, text = m.groups() - if not text: - if kind not in ('else', 'endif'): - raise ValueError(f'missing text in directive {line!r}') - elif kind in ('else', 'endif', 'define'): - raise ValueError(f'unexpected text in directive {line!r}') - if kind == 'include': - directive = Include(text) - elif kind in IfDirective.KINDS: - directive = IfDirective(kind, text) - else: - directive = OtherDirective(kind, text) - directive.validate() - return directive - - -class PreprocessorDirective(util._NTBase): - """The base class for directives.""" - - __slots__ = () - - KINDS = frozenset([ - 'include', - 'pragma', - 'error', 'warning', - 'define', 'undef', - 'if', 'ifdef', 'ifndef', 'elseif', 'else', 'endif', - '__FILE__', '__DATE__', '__LINE__', '__TIME__', '__TIMESTAMP__', - ]) - - @property - def text(self): - return ' '.join(v for v in self[1:] if v and v.strip()) or None - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - super().validate() - - if not self.kind: - raise TypeError('missing kind') - elif self.kind not in self.KINDS: - raise ValueError - - # text can be anything, including None. - - -class Constant(PreprocessorDirective, - namedtuple('Constant', 'kind name value')): - """A single "constant" directive ("define").""" - - __slots__ = () - - def __new__(cls, name, value=None): - self = super().__new__( - cls, - 'define', - name=_coerce_str(name) or None, - value=_coerce_str(value) or None, - ) - return self - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - super().validate() - - if not self.name: - raise TypeError('missing name') - elif not IDENTIFIER_RE.match(self.name): - raise ValueError(f'name must be identifier, got {self.name!r}') - - # value can be anything, including None - - -class Macro(PreprocessorDirective, - namedtuple('Macro', 'kind name args body')): - """A single "macro" directive ("define").""" - - __slots__ = () - - def __new__(cls, name, args, body=None): - # "args" must be a string or an iterable of strings (or "empty"). - if isinstance(args, str): - args = [v.strip() for v in args.split(',')] - if args: - args = tuple(_coerce_str(a) or None for a in args) - self = super().__new__( - cls, - kind='define', - name=_coerce_str(name) or None, - args=args if args else (), - body=_coerce_str(body) or None, - ) - return self - - @property - def text(self): - if self.body: - return f'{self.name}({", ".join(self.args)}) {self.body}' - else: - return f'{self.name}({", ".join(self.args)})' - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - super().validate() - - if not self.name: - raise TypeError('missing name') - elif not IDENTIFIER_RE.match(self.name): - raise ValueError(f'name must be identifier, got {self.name!r}') - - for arg in self.args: - if not arg: - raise ValueError(f'missing arg in {self.args}') - elif not IDENTIFIER_RE.match(arg): - raise ValueError(f'arg must be identifier, got {arg!r}') - - # body can be anything, including None - - -class IfDirective(PreprocessorDirective, - namedtuple('IfDirective', 'kind condition')): - """A single conditional directive (e.g. "if", "ifdef"). - - This only includes directives that actually provide conditions. The - related directives "else" and "endif" are covered by OtherDirective - instead. - """ - - __slots__ = () - - KINDS = frozenset([ - 'if', - 'ifdef', - 'ifndef', - 'elseif', - ]) - - @classmethod - def _condition_from_raw(cls, raw, kind): - #return Condition.from_raw(raw, _kind=kind) - condition = _coerce_str(raw) - if not condition: - return None - - if kind == 'ifdef': - condition = f'defined({condition})' - elif kind == 'ifndef': - condition = f'! defined({condition})' - - return condition - - def __new__(cls, kind, condition): - kind = _coerce_str(kind) - self = super().__new__( - cls, - kind=kind or None, - condition=cls._condition_from_raw(condition, kind), - ) - return self - - @property - def text(self): - if self.kind == 'ifdef': - return self.condition[8:-1] # strip "defined(" - elif self.kind == 'ifndef': - return self.condition[10:-1] # strip "! defined(" - else: - return self.condition - #return str(self.condition) - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - super().validate() - - if not self.condition: - raise TypeError('missing condition') - #else: - # for cond in self.condition: - # if not cond: - # raise ValueError(f'missing condition in {self.condition}') - # cond.validate() - # if self.kind in ('ifdef', 'ifndef'): - # if len(self.condition) != 1: - # raise ValueError('too many condition') - # if self.kind == 'ifdef': - # if not self.condition[0].startswith('defined '): - # raise ValueError('bad condition') - # else: - # if not self.condition[0].startswith('! defined '): - # raise ValueError('bad condition') - - -class Include(PreprocessorDirective, - namedtuple('Include', 'kind file')): - """A single "include" directive. - - Supported "file" values are either follow the bracket style - (<stdio>) or double quotes ("spam.h"). - """ - - __slots__ = () - - def __new__(cls, file): - self = super().__new__( - cls, - kind='include', - file=_coerce_str(file) or None, - ) - return self - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - super().validate() - - if not self.file: - raise TypeError('missing file') - - -class OtherDirective(PreprocessorDirective, - namedtuple('OtherDirective', 'kind text')): - """A single directive not covered by another class. - - This includes the "else", "endif", and "undef" directives, which are - otherwise inherently related to the directives covered by the - Constant, Macro, and IfCondition classes. - - Note that all directives must have a text value, except for "else" - and "endif" (which must have no text). - """ - - __slots__ = () - - KINDS = PreprocessorDirective.KINDS - {'include', 'define'} - IfDirective.KINDS - - def __new__(cls, kind, text): - self = super().__new__( - cls, - kind=_coerce_str(kind) or None, - text=_coerce_str(text) or None, - ) - return self - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - super().validate() - - if self.text: - if self.kind in ('else', 'endif'): - raise ValueError('unexpected text in directive') - elif self.kind not in ('else', 'endif'): - raise TypeError('missing text') - - -############################# -# iterating lines - -def _recompute_conditions(directive, ifstack): - if directive.kind in ('if', 'ifdef', 'ifndef'): - ifstack.append( - ([], directive.condition)) - elif directive.kind == 'elseif': - if ifstack: - negated, active = ifstack.pop() - if active: - negated.append(active) - else: - negated = [] - ifstack.append( - (negated, directive.condition)) - elif directive.kind == 'else': - if ifstack: - negated, active = ifstack.pop() - if active: - negated.append(active) - ifstack.append( - (negated, None)) - elif directive.kind == 'endif': - if ifstack: - ifstack.pop() - - conditions = [] - for negated, active in ifstack: - for condition in negated: - conditions.append(f'! ({condition})') - if active: - conditions.append(active) - return tuple(conditions) - - -def _iter_clean_lines(lines): - lines = iter(enumerate(lines, 1)) - for lno, line in lines: - # Handle line continuations. - while line.endswith(CONTINUATION): - try: - lno, _line = next(lines) - except StopIteration: - break - line = line[:-len(CONTINUATION)] + ' ' + _line - - # Deal with comments. - after = line - line = '' - while True: - # Look for a comment. - before, begin, remainder = after.partition('/*') - if '//' in before: - before, _, _ = before.partition('//') - line += before + ' ' # per the C99 spec - break - line += before - if not begin: - break - line += ' ' # per the C99 spec - - # Go until we find the end of the comment. - _, end, after = remainder.partition('*/') - while not end: - try: - lno, remainder = next(lines) - except StopIteration: - raise Exception('unterminated comment') - _, end, after = remainder.partition('*/') - - yield lno, line - - -def iter_lines(lines, *, - _iter_clean_lines=_iter_clean_lines, - _parse_directive=_parse_directive, - _recompute_conditions=_recompute_conditions, - ): - """Yield (lno, line, directive, active conditions) for each given line. - - This is effectively a subset of the operations taking place in - translation phases 2-4 from the C99 spec (ISO/IEC 9899:TC2); see - section 5.1.1.2. Line continuations are removed and comments - replaced with a single space. (In both cases "lno" will be the last - line involved.) Otherwise each line is returned as-is. - - "lno" is the (1-indexed) line number for the line. - - "directive" will be a PreprocessorDirective or None, depending on - whether or not there is a directive on the line. - - "active conditions" is the set of preprocessor conditions (e.g. - "defined()") under which the current line of code will be included - in compilation. That set is derived from every conditional - directive block (e.g. "if defined()", "ifdef", "else") containing - that line. That includes nested directives. Note that the - current line does not affect the active conditions for iteself. - It only impacts subsequent lines. That applies to directives - that close blocks (e.g. "endif") just as much as conditional - directvies. Also note that "else" and "elseif" directives - update the active conditions (for later lines), rather than - adding to them. - """ - ifstack = [] - conditions = () - for lno, line in _iter_clean_lines(lines): - stripped = line.strip() - if not stripped.startswith('#'): - yield lno, line, None, conditions - continue - - directive = '#' + stripped[1:].lstrip() - while ' ' in directive: - directive = directive.replace(' ', ' ') - directive = _parse_directive(directive) - yield lno, line, directive, conditions - - if directive.kind in ('else', 'endif'): - conditions = _recompute_conditions(directive, ifstack) - elif isinstance(directive, IfDirective): - conditions = _recompute_conditions(directive, ifstack) - - -############################# -# running (platform-specific?) - -def _gcc(filename, *, - _get_argv=(lambda: _get_gcc_argv()), - _run=util.run_cmd, - ): - argv = _get_argv() - argv.extend([ - '-E', filename, - ]) - output = _run(argv) - return output - - -def _get_gcc_argv(*, - _open=open, - _run=util.run_cmd, - ): - with _open('/tmp/print.mk', 'w') as tmpfile: - tmpfile.write('print-%:\n') - #tmpfile.write('\t@echo $* = $($*)\n') - tmpfile.write('\t@echo $($*)\n') - argv = ['/usr/bin/make', - '-f', 'Makefile', - '-f', '/tmp/print.mk', - 'print-CC', - 'print-PY_CORE_CFLAGS', - ] - output = _run(argv) - gcc, cflags = output.strip().splitlines() - argv = shlex.split(gcc.strip()) - cflags = shlex.split(cflags.strip()) - return argv + cflags - - -def run(filename, *, - _gcc=_gcc, - ): - """Return the text of the given file after running the preprocessor.""" - return _gcc(filename) diff --git a/Tools/c-analyzer/c_analyzer/parser/source.py b/Tools/c-analyzer/c_analyzer/parser/source.py deleted file mode 100644 index f8998c8a338..00000000000 --- a/Tools/c-analyzer/c_analyzer/parser/source.py +++ /dev/null @@ -1,34 +0,0 @@ -from . import preprocessor - - -def iter_clean_lines(lines): - incomment = False - for line in lines: - # Deal with comments. - if incomment: - _, sep, line = line.partition('*/') - if sep: - incomment = False - continue - line, _, _ = line.partition('//') - line, sep, remainder = line.partition('/*') - if sep: - _, sep, after = remainder.partition('*/') - if not sep: - incomment = True - continue - line += ' ' + after - - # Ignore blank lines and leading/trailing whitespace. - line = line.strip() - if not line: - continue - - yield line - - -def iter_lines(filename, *, - preprocess=preprocessor.run, - ): - content = preprocess(filename) - return iter(content.splitlines()) diff --git a/Tools/c-analyzer/c_analyzer/symbols/__init__.py b/Tools/c-analyzer/c_analyzer/symbols/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 --- a/Tools/c-analyzer/c_analyzer/symbols/__init__.py +++ /dev/null diff --git a/Tools/c-analyzer/c_analyzer/symbols/_nm.py b/Tools/c-analyzer/c_analyzer/symbols/_nm.py deleted file mode 100644 index f3a75a6d4ba..00000000000 --- a/Tools/c-analyzer/c_analyzer/symbols/_nm.py +++ /dev/null @@ -1,117 +0,0 @@ -import os.path -import shutil - -from c_analyzer.common import util, info - -from .info import Symbol - - -# XXX need tests: -# * iter_symbols - -NM_KINDS = { - 'b': Symbol.KIND.VARIABLE, # uninitialized - 'd': Symbol.KIND.VARIABLE, # initialized - #'g': Symbol.KIND.VARIABLE, # uninitialized - #'s': Symbol.KIND.VARIABLE, # initialized - 't': Symbol.KIND.FUNCTION, - } - -SPECIAL_SYMBOLS = { - # binary format (e.g. ELF) - '__bss_start', - '__data_start', - '__dso_handle', - '_DYNAMIC', - '_edata', - '_end', - '__environ@@GLIBC_2.2.5', - '_GLOBAL_OFFSET_TABLE_', - '__JCR_END__', - '__JCR_LIST__', - '__TMC_END__', - } - - -def _is_special_symbol(name): - if name in SPECIAL_SYMBOLS: - return True - if '@@GLIBC' in name: - return True - return False - - -def iter_symbols(binfile, *, - nm=None, - handle_id=None, - _which=shutil.which, - _run=util.run_cmd, - ): - """Yield a Symbol for each relevant entry reported by the "nm" command.""" - if nm is None: - nm = _which('nm') - if not nm: - raise NotImplementedError - if handle_id is None: - handle_id = info.ID - - argv = [nm, - '--line-numbers', - binfile, - ] - try: - output = _run(argv) - except Exception: - if nm is None: - # XXX Use dumpbin.exe /SYMBOLS on Windows. - raise NotImplementedError - raise - for line in output.splitlines(): - (name, kind, external, filename, funcname, - ) = _parse_nm_line(line) - if kind != Symbol.KIND.VARIABLE: - continue - elif _is_special_symbol(name): - continue - yield Symbol( - id=handle_id(filename, funcname, name), - kind=kind, - external=external, - ) - - -def _parse_nm_line(line): - _origline = line - _, _, line = line.partition(' ') # strip off the address - line = line.strip() - - kind, _, line = line.partition(' ') - line = line.strip() - external = kind.isupper() - kind = NM_KINDS.get(kind.lower(), Symbol.KIND.OTHER) - - name, _, filename = line.partition('\t') - name = name.strip() - if filename: - filename = os.path.relpath(filename.partition(':')[0]) - else: - filename = info.UNKNOWN - - name, islocal = _parse_nm_name(name, kind) - funcname = info.UNKNOWN if islocal else None - return name, kind, external, filename, funcname - - -def _parse_nm_name(name, kind): - if kind != Symbol.KIND.VARIABLE: - return name, None - if _is_special_symbol(name): - return name, None - - actual, sep, digits = name.partition('.') - if not sep: - return name, False - - if not digits.isdigit(): - raise Exception(f'got bogus name {name}') - return actual, True diff --git a/Tools/c-analyzer/c_analyzer/symbols/find.py b/Tools/c-analyzer/c_analyzer/symbols/find.py deleted file mode 100644 index 85646523f7a..00000000000 --- a/Tools/c-analyzer/c_analyzer/symbols/find.py +++ /dev/null @@ -1,175 +0,0 @@ -import os -import os.path -import shutil - -from ..common import files -from ..common.info import UNKNOWN, ID -from ..parser import find as p_find - -from . import _nm -from .info import Symbol - -# XXX need tests: -# * get_resolver() -# * get_resolver_from_dirs() -# * symbol() -# * symbols() -# * variables() - - -def _resolve_known(symbol, knownvars): - for varid in knownvars: - if symbol.match(varid): - break - else: - return None - return knownvars.pop(varid) - - -def get_resolver(filenames=None, known=None, *, - handle_var, - check_filename=None, - perfilecache=None, - preprocessed=False, - _from_source=p_find.variable_from_id, - ): - """Return a "resolver" func for the given known vars/types and filenames. - - "handle_var" is a callable that takes (ID, decl) and returns a - Variable. Variable.from_id is a suitable callable. - - The returned func takes a single Symbol and returns a corresponding - Variable. If the symbol was located then the variable will be - valid, populated with the corresponding information. Otherwise None - is returned. - """ - knownvars = (known or {}).get('variables') - if knownvars: - knownvars = dict(knownvars) # a copy - if filenames: - if check_filename is None: - filenames = list(filenames) - def check_filename(filename): - return filename in filenames - def resolve(symbol): - # XXX Check "found" instead? - if not check_filename(symbol.filename): - return None - found = _resolve_known(symbol, knownvars) - if found is None: - #return None - varid, decl = _from_source(symbol, filenames, - perfilecache=perfilecache, - preprocessed=preprocessed, - ) - found = handle_var(varid, decl) - return found - else: - def resolve(symbol): - return _resolve_known(symbol, knownvars) - elif filenames: - def resolve(symbol): - varid, decl = _from_source(symbol, filenames, - perfilecache=perfilecache, - preprocessed=preprocessed, - ) - return handle_var(varid, decl) - else: - def resolve(symbol): - return None - return resolve - - -def get_resolver_from_dirs(dirnames, known=None, *, - handle_var, - suffixes=('.c',), - perfilecache=None, - preprocessed=False, - _iter_files=files.iter_files_by_suffix, - _get_resolver=get_resolver, - ): - """Return a "resolver" func for the given known vars/types and filenames. - - "dirnames" should be absolute paths. If not then they will be - resolved relative to CWD. - - See get_resolver(). - """ - dirnames = [d if d.endswith(os.path.sep) else d + os.path.sep - for d in dirnames] - filenames = _iter_files(dirnames, suffixes) - def check_filename(filename): - for dirname in dirnames: - if filename.startswith(dirname): - return True - else: - return False - return _get_resolver(filenames, known, - handle_var=handle_var, - check_filename=check_filename, - perfilecache=perfilecache, - preprocessed=preprocessed, - ) - - -def symbol(symbol, filenames, known=None, *, - perfilecache=None, - preprocessed=False, - handle_id=None, - _get_resolver=get_resolver, - ): - """Return a Variable for the one matching the given symbol. - - "symbol" can be one of several objects: - - * Symbol - use the contained info - * name (str) - look for a global variable with that name - * (filename, name) - look for named global in file - * (filename, funcname, name) - look for named local in file - - A name is always required. If the filename is None, "", or - "UNKNOWN" then all files will be searched. If the funcname is - "" or "UNKNOWN" then only local variables will be searched for. - """ - resolve = _get_resolver(known, filenames, - handle_id=handle_id, - perfilecache=perfilecache, - preprocessed=preprocessed, - ) - return resolve(symbol) - - -def _get_platform_tool(): - if os.name == 'nt': - # XXX Support this. - raise NotImplementedError - elif nm := shutil.which('nm'): - return lambda b, hi: _nm.iter_symbols(b, nm=nm, handle_id=hi) - else: - raise NotImplementedError - - -def symbols(binfile, *, - handle_id=None, - _file_exists=os.path.exists, - _get_platform_tool=_get_platform_tool, - ): - """Yield a Symbol for each one found in the binary.""" - if not _file_exists(binfile): - raise Exception('executable missing (need to build it first?)') - - _iter_symbols = _get_platform_tool() - yield from _iter_symbols(binfile, handle_id) - - -def variables(binfile, *, - resolve, - handle_id=None, - _iter_symbols=symbols, - ): - """Yield (Variable, Symbol) for each found symbol.""" - for symbol in _iter_symbols(binfile, handle_id=handle_id): - if symbol.kind != Symbol.KIND.VARIABLE: - continue - var = resolve(symbol) or None - yield var, symbol diff --git a/Tools/c-analyzer/c_analyzer/symbols/info.py b/Tools/c-analyzer/c_analyzer/symbols/info.py deleted file mode 100644 index 96a251abb7c..00000000000 --- a/Tools/c-analyzer/c_analyzer/symbols/info.py +++ /dev/null @@ -1,51 +0,0 @@ -from collections import namedtuple - -from c_analyzer.common.info import ID -from c_analyzer.common.util import classonly, _NTBase - - -class Symbol(_NTBase, namedtuple('Symbol', 'id kind external')): - """Info for a single compilation symbol.""" - - __slots__ = () - - class KIND: - VARIABLE = 'variable' - FUNCTION = 'function' - OTHER = 'other' - - @classonly - def from_name(cls, name, filename=None, kind=KIND.VARIABLE, external=None): - """Return a new symbol based on the given name.""" - id = ID(filename, None, name) - return cls(id, kind, external) - - def __new__(cls, id, kind=KIND.VARIABLE, external=None): - self = super().__new__( - cls, - id=ID.from_raw(id), - kind=str(kind) if kind else None, - external=bool(external) if external is not None else None, - ) - return self - - def __hash__(self): - return hash(self.id) - - def __getattr__(self, name): - return getattr(self.id, name) - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - if not self.id: - raise TypeError('missing id') - else: - self.id.validate() - - if not self.kind: - raise TypeError('missing kind') - elif self.kind not in vars(self.KIND).values(): - raise ValueError(f'unsupported kind {self.kind}') - - if self.external is None: - raise TypeError('missing external') diff --git a/Tools/c-analyzer/c_analyzer/variables/__init__.py b/Tools/c-analyzer/c_analyzer/variables/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 --- a/Tools/c-analyzer/c_analyzer/variables/__init__.py +++ /dev/null diff --git a/Tools/c-analyzer/c_analyzer/variables/find.py b/Tools/c-analyzer/c_analyzer/variables/find.py deleted file mode 100644 index 3fe7284fc00..00000000000 --- a/Tools/c-analyzer/c_analyzer/variables/find.py +++ /dev/null @@ -1,75 +0,0 @@ -from ..common import files -from ..common.info import UNKNOWN -from ..parser import ( - find as p_find, - ) -from ..symbols import ( - info as s_info, - find as s_find, - ) -from .info import Variable - -# XXX need tests: -# * vars_from_source - - -def _remove_cached(cache, var): - if not cache: - return - try: - cached = cache[var.filename] - cached.remove(var) - except (KeyError, IndexError): - pass - - -def vars_from_binary(binfile, *, - known=None, - filenames=None, - handle_id=None, - check_filename=None, - handle_var=Variable.from_id, - _iter_vars=s_find.variables, - _get_symbol_resolver=s_find.get_resolver, - ): - """Yield a Variable for each found Symbol. - - Details are filled in from the given "known" variables and types. - """ - cache = {} - resolve = _get_symbol_resolver(filenames, known, - handle_var=handle_var, - check_filename=check_filename, - perfilecache=cache, - ) - for var, symbol in _iter_vars(binfile, - resolve=resolve, - handle_id=handle_id, - ): - if var is None: - var = Variable(symbol.id, UNKNOWN, UNKNOWN) - yield var - _remove_cached(cache, var) - - -def vars_from_source(filenames, *, - preprocessed=None, - known=None, - handle_id=None, - handle_var=Variable.from_id, - iter_vars=p_find.variables, - ): - """Yield a Variable for each declaration in the raw source code. - - Details are filled in from the given "known" variables and types. - """ - cache = {} - for varid, decl in iter_vars(filenames or (), - perfilecache=cache, - preprocessed=preprocessed, - known=known, - handle_id=handle_id, - ): - var = handle_var(varid, decl) - yield var - _remove_cached(cache, var) diff --git a/Tools/c-analyzer/c_analyzer/variables/info.py b/Tools/c-analyzer/c_analyzer/variables/info.py deleted file mode 100644 index 336a523c7a2..00000000000 --- a/Tools/c-analyzer/c_analyzer/variables/info.py +++ /dev/null @@ -1,93 +0,0 @@ -from collections import namedtuple - -from ..common.info import ID, UNKNOWN -from ..common.util import classonly, _NTBase - - -def normalize_vartype(vartype): - """Return the canonical form for a variable type (or func signature).""" - # We allow empty strring through for semantic reasons. - if vartype is None: - return None - - # XXX finish! - # XXX Return (modifiers, type, pointer)? - return str(vartype) - - -# XXX Variable.vartype -> decl (Declaration). - -class Variable(_NTBase, - namedtuple('Variable', 'id storage vartype')): - """Information about a single variable declaration.""" - - __slots__ = () - - STORAGE = ( - 'static', - 'extern', - 'implicit', - 'local', - ) - - @classonly - def from_parts(cls, filename, funcname, name, decl, storage=None): - varid = ID(filename, funcname, name) - if storage is None: - self = cls.from_id(varid, decl) - else: - self = cls(varid, storage, decl) - return self - - @classonly - def from_id(cls, varid, decl): - from ..parser.declarations import extract_storage - storage = extract_storage(decl, infunc=varid.funcname) - return cls(varid, storage, decl) - - def __new__(cls, id, storage, vartype): - self = super().__new__( - cls, - id=ID.from_raw(id), - storage=str(storage) if storage else None, - vartype=normalize_vartype(vartype) if vartype else None, - ) - return self - - def __hash__(self): - return hash(self.id) - - def __getattr__(self, name): - return getattr(self.id, name) - - def _validate_id(self): - if not self.id: - raise TypeError('missing id') - - if not self.filename or self.filename == UNKNOWN: - raise TypeError(f'id missing filename ({self.id})') - - if self.funcname and self.funcname == UNKNOWN: - raise TypeError(f'id missing funcname ({self.id})') - - self.id.validate() - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - self._validate_id() - - if self.storage is None or self.storage == UNKNOWN: - raise TypeError('missing storage') - elif self.storage not in self.STORAGE: - raise ValueError(f'unsupported storage {self.storage:r}') - - if self.vartype is None or self.vartype == UNKNOWN: - raise TypeError('missing vartype') - - @property - def isglobal(self): - return self.storage != 'local' - - @property - def isconst(self): - return 'const' in self.vartype.split() diff --git a/Tools/c-analyzer/c_analyzer/variables/known.py b/Tools/c-analyzer/c_analyzer/variables/known.py deleted file mode 100644 index aa2934a069e..00000000000 --- a/Tools/c-analyzer/c_analyzer/variables/known.py +++ /dev/null @@ -1,91 +0,0 @@ -import csv - -from ..common.info import ID, UNKNOWN -from ..common.util import read_tsv -from .info import Variable - - -# XXX need tests: -# * read_file() -# * look_up_variable() - - -COLUMNS = ('filename', 'funcname', 'name', 'kind', 'declaration') -HEADER = '\t'.join(COLUMNS) - - -def read_file(infile, *, - _read_tsv=read_tsv, - ): - """Yield (kind, id, decl) for each row in the data file. - - The caller is responsible for validating each row. - """ - for row in _read_tsv(infile, HEADER): - filename, funcname, name, kind, declaration = row - if not funcname or funcname == '-': - funcname = None - id = ID(filename, funcname, name) - yield kind, id, declaration - - -def from_file(infile, *, - handle_var=Variable.from_id, - _read_file=read_file, - ): - """Return the info for known declarations in the given file.""" - known = { - 'variables': {}, - #'types': {}, - #'constants': {}, - #'macros': {}, - } - for kind, id, decl in _read_file(infile): - if kind == 'variable': - values = known['variables'] - value = handle_var(id, decl) - else: - raise ValueError(f'unsupported kind in row {row}') - value.validate() - values[id] = value - return known - - -def look_up_variable(varid, knownvars, *, - match_files=(lambda f1, f2: f1 == f2), - ): - """Return the known Variable matching the given ID. - - "knownvars" is a mapping of ID to Variable. - - "match_files" is used to verify if two filenames point to - the same file. - - If no match is found then None is returned. - """ - if not knownvars: - return None - - if varid.funcname == UNKNOWN: - if not varid.filename or varid.filename == UNKNOWN: - for varid in knownvars: - if not varid.funcname: - continue - if varid.name == varid.name: - return knownvars[varid] - else: - return None - else: - for varid in knownvars: - if not varid.funcname: - continue - if not match_files(varid.filename, varid.filename): - continue - if varid.name == varid.name: - return knownvars[varid] - else: - return None - elif not varid.filename or varid.filename == UNKNOWN: - raise NotImplementedError - else: - return knownvars.get(varid.id) diff --git a/Tools/c-analyzer/c_common/__init__.py b/Tools/c-analyzer/c_common/__init__.py new file mode 100644 index 00000000000..a4c3bb24230 --- /dev/null +++ b/Tools/c-analyzer/c_common/__init__.py @@ -0,0 +1,2 @@ + +NOT_SET = object() diff --git a/Tools/c-analyzer/c_analyzer/common/util.py b/Tools/c-analyzer/c_common/clsutil.py index 43d0bb6e665..aa5f6b9831d 100644 --- a/Tools/c-analyzer/c_analyzer/common/util.py +++ b/Tools/c-analyzer/c_common/clsutil.py @@ -1,70 +1,7 @@ -import csv -import subprocess - _NOT_SET = object() -def run_cmd(argv, **kwargs): - proc = subprocess.run( - argv, - #capture_output=True, - #stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - text=True, - check=True, - **kwargs - ) - return proc.stdout - - -def read_tsv(infile, header, *, - _open=open, - _get_reader=csv.reader, - ): - """Yield each row of the given TSV (tab-separated) file.""" - if isinstance(infile, str): - with _open(infile, newline='') as infile: - yield from read_tsv(infile, header, - _open=_open, - _get_reader=_get_reader, - ) - return - lines = iter(infile) - - # Validate the header. - try: - actualheader = next(lines).strip() - except StopIteration: - actualheader = '' - if actualheader != header: - raise ValueError(f'bad header {actualheader!r}') - - for row in _get_reader(lines, delimiter='\t'): - yield tuple(v.strip() for v in row) - - -def write_tsv(outfile, header, rows, *, - _open=open, - _get_writer=csv.writer, - ): - """Write each of the rows to the given TSV (tab-separated) file.""" - if isinstance(outfile, str): - with _open(outfile, 'w', newline='') as outfile: - return write_tsv(outfile, header, rows, - _open=_open, - _get_writer=_get_writer, - ) - - if isinstance(header, str): - header = header.split('\t') - writer = _get_writer(outfile, delimiter='\t') - writer.writerow(header) - for row in rows: - writer.writerow('' if v is None else str(v) - for v in row) - - class Slot: """A descriptor that provides a slot. @@ -178,66 +115,3 @@ class classonly: raise AttributeError(self.name) # called on the class return self.getter(None, cls) - - -class _NTBase: - - __slots__ = () - - @classonly - def from_raw(cls, raw): - if not raw: - return None - elif isinstance(raw, cls): - return raw - elif isinstance(raw, str): - return cls.from_string(raw) - else: - if hasattr(raw, 'items'): - return cls(**raw) - try: - args = tuple(raw) - except TypeError: - pass - else: - return cls(*args) - raise NotImplementedError - - @classonly - def from_string(cls, value): - """Return a new instance based on the given string.""" - raise NotImplementedError - - @classmethod - def _make(cls, iterable): # The default _make() is not subclass-friendly. - return cls.__new__(cls, *iterable) - - # XXX Always validate? - #def __init__(self, *args, **kwargs): - # self.validate() - - # XXX The default __repr__() is not subclass-friendly (where the name changes). - #def __repr__(self): - # _, _, sig = super().__repr__().partition('(') - # return f'{self.__class__.__name__}({sig}' - - # To make sorting work with None: - def __lt__(self, other): - try: - return super().__lt__(other) - except TypeError: - if None in self: - return True - elif None in other: - return False - else: - raise - - def validate(self): - return - - # XXX Always validate? - #def _replace(self, **kwargs): - # obj = super()._replace(**kwargs) - # obj.validate() - # return obj diff --git a/Tools/c-analyzer/c_common/fsutil.py b/Tools/c-analyzer/c_common/fsutil.py new file mode 100644 index 00000000000..56023f33523 --- /dev/null +++ b/Tools/c-analyzer/c_common/fsutil.py @@ -0,0 +1,388 @@ +import fnmatch +import glob +import os +import os.path +import shutil +import stat + +from .iterutil import iter_many + + +C_SOURCE_SUFFIXES = ('.c', '.h') + + +def create_backup(old, backup=None): + if isinstance(old, str): + filename = old + else: + filename = getattr(old, 'name', None) + if not filename: + return None + if not backup or backup is True: + backup = f'{filename}.bak' + try: + shutil.copyfile(filename, backup) + except FileNotFoundError as exc: + if exc.filename != filename: + raise # re-raise + backup = None + return backup + + +################################## +# find files + +def match_glob(filename, pattern): + if fnmatch.fnmatch(filename, pattern): + return True + + # fnmatch doesn't handle ** quite right. It will not match the + # following: + # + # ('x/spam.py', 'x/**/*.py') + # ('spam.py', '**/*.py') + # + # though it *will* match the following: + # + # ('x/y/spam.py', 'x/**/*.py') + # ('x/spam.py', '**/*.py') + + if '**/' not in pattern: + return False + + # We only accommodate the single-"**" case. + return fnmatch.fnmatch(filename, pattern.replace('**/', '', 1)) + + +def iter_filenames(filenames, *, + start=None, + include=None, + exclude=None, + ): + onempty = Exception('no filenames provided') + for filename, solo in iter_many(filenames, onempty): + check, start = _get_check(filename, start, include, exclude) + yield filename, check, solo +# filenames = iter(filenames or ()) +# try: +# first = next(filenames) +# except StopIteration: +# raise Exception('no filenames provided') +# try: +# second = next(filenames) +# except StopIteration: +# check, _ = _get_check(first, start, include, exclude) +# yield first, check, False +# return +# +# check, start = _get_check(first, start, include, exclude) +# yield first, check, True +# check, start = _get_check(second, start, include, exclude) +# yield second, check, True +# for filename in filenames: +# check, start = _get_check(filename, start, include, exclude) +# yield filename, check, True + + +def expand_filenames(filenames): + for filename in filenames: + # XXX Do we need to use glob.escape (a la commit 9355868458, GH-20994)? + if '**/' in filename: + yield from glob.glob(filename.replace('**/', '')) + yield from glob.glob(filename) + + +def _get_check(filename, start, include, exclude): + if start and filename != start: + return (lambda: '<skipped>'), start + else: + def check(): + if _is_excluded(filename, exclude, include): + return '<excluded>' + return None + return check, None + + +def _is_excluded(filename, exclude, include): + if include: + for included in include: + if match_glob(filename, included): + return False + return True + elif exclude: + for excluded in exclude: + if match_glob(filename, excluded): + return True + return False + else: + return False + + +def _walk_tree(root, *, + _walk=os.walk, + ): + # A wrapper around os.walk that resolves the filenames. + for parent, _, names in _walk(root): + for name in names: + yield os.path.join(parent, name) + + +def walk_tree(root, *, + suffix=None, + walk=_walk_tree, + ): + """Yield each file in the tree under the given directory name. + + If "suffix" is provided then only files with that suffix will + be included. + """ + if suffix and not isinstance(suffix, str): + raise ValueError('suffix must be a string') + + for filename in walk(root): + if suffix and not filename.endswith(suffix): + continue + yield filename + + +def glob_tree(root, *, + suffix=None, + _glob=glob.iglob, + ): + """Yield each file in the tree under the given directory name. + + If "suffix" is provided then only files with that suffix will + be included. + """ + suffix = suffix or '' + if not isinstance(suffix, str): + raise ValueError('suffix must be a string') + + for filename in _glob(f'{root}/*{suffix}'): + yield filename + for filename in _glob(f'{root}/**/*{suffix}'): + yield filename + + +def iter_files(root, suffix=None, relparent=None, *, + get_files=os.walk, + _glob=glob_tree, + _walk=walk_tree, + ): + """Yield each file in the tree under the given directory name. + + If "root" is a non-string iterable then do the same for each of + those trees. + + If "suffix" is provided then only files with that suffix will + be included. + + if "relparent" is provided then it is used to resolve each + filename as a relative path. + """ + if not isinstance(root, str): + roots = root + for root in roots: + yield from iter_files(root, suffix, relparent, + get_files=get_files, + _glob=_glob, _walk=_walk) + return + + # Use the right "walk" function. + if get_files in (glob.glob, glob.iglob, glob_tree): + get_files = _glob + else: + _files = _walk_tree if get_files in (os.walk, walk_tree) else get_files + get_files = (lambda *a, **k: _walk(*a, walk=_files, **k)) + + # Handle a single suffix. + if suffix and not isinstance(suffix, str): + filenames = get_files(root) + suffix = tuple(suffix) + else: + filenames = get_files(root, suffix=suffix) + suffix = None + + for filename in filenames: + if suffix and not isinstance(suffix, str): # multiple suffixes + if not filename.endswith(suffix): + continue + if relparent: + filename = os.path.relpath(filename, relparent) + yield filename + + +def iter_files_by_suffix(root, suffixes, relparent=None, *, + walk=walk_tree, + _iter_files=iter_files, + ): + """Yield each file in the tree that has the given suffixes. + + Unlike iter_files(), the results are in the original suffix order. + """ + if isinstance(suffixes, str): + suffixes = [suffixes] + # XXX Ignore repeated suffixes? + for suffix in suffixes: + yield from _iter_files(root, suffix, relparent) + + +################################## +# file info + +# XXX posix-only? + +S_IRANY = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH +S_IWANY = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH +S_IXANY = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + + +def is_readable(file, *, user=None, check=False): + filename, st, mode = _get_file_info(file) + if check: + try: + okay = _check_file(filename, S_IRANY) + except NotImplementedError: + okay = NotImplemented + if okay is not NotImplemented: + return okay + # Fall back to checking the mode. + return _check_mode(st, mode, S_IRANY, user) + + +def is_writable(file, *, user=None, check=False): + filename, st, mode = _get_file_info(file) + if check: + try: + okay = _check_file(filename, S_IWANY) + except NotImplementedError: + okay = NotImplemented + if okay is not NotImplemented: + return okay + # Fall back to checking the mode. + return _check_mode(st, mode, S_IWANY, user) + + +def is_executable(file, *, user=None, check=False): + filename, st, mode = _get_file_info(file) + if check: + try: + okay = _check_file(filename, S_IXANY) + except NotImplementedError: + okay = NotImplemented + if okay is not NotImplemented: + return okay + # Fall back to checking the mode. + return _check_mode(st, mode, S_IXANY, user) + + +def _get_file_info(file): + filename = st = mode = None + if isinstance(file, int): + mode = file + elif isinstance(file, os.stat_result): + st = file + else: + if isinstance(file, str): + filename = file + elif hasattr(file, 'name') and os.path.exists(file.name): + filename = file.name + else: + raise NotImplementedError(file) + st = os.stat(filename) + return filename, st, mode or st.st_mode + + +def _check_file(filename, check): + if not isinstance(filename, str): + raise Exception(f'filename required to check file, got {filename}') + if check & S_IRANY: + flags = os.O_RDONLY + elif check & S_IWANY: + flags = os.O_WRONLY + elif check & S_IXANY: + # We can worry about S_IXANY later + return NotImplemented + else: + raise NotImplementedError(check) + + try: + fd = os.open(filename, flags) + except PermissionError: + return False + # We do not ignore other exceptions. + else: + os.close(fd) + return True + + +def _get_user_info(user): + import pwd + username = uid = gid = groups = None + if user is None: + uid = os.geteuid() + #username = os.getlogin() + username = pwd.getpwuid(uid)[0] + gid = os.getgid() + groups = os.getgroups() + else: + if isinstance(user, int): + uid = user + entry = pwd.getpwuid(uid) + username = entry.pw_name + elif isinstance(user, str): + username = user + entry = pwd.getpwnam(username) + uid = entry.pw_uid + else: + raise NotImplementedError(user) + gid = entry.pw_gid + os.getgrouplist(username, gid) + return username, uid, gid, groups + + +def _check_mode(st, mode, check, user): + orig = check + _, uid, gid, groups = _get_user_info(user) + if check & S_IRANY: + check -= S_IRANY + matched = False + if mode & stat.S_IRUSR: + if st.st_uid == uid: + matched = True + if mode & stat.S_IRGRP: + if st.st_uid == gid or st.st_uid in groups: + matched = True + if mode & stat.S_IROTH: + matched = True + if not matched: + return False + if check & S_IWANY: + check -= S_IWANY + matched = False + if mode & stat.S_IWUSR: + if st.st_uid == uid: + matched = True + if mode & stat.S_IWGRP: + if st.st_uid == gid or st.st_uid in groups: + matched = True + if mode & stat.S_IWOTH: + matched = True + if not matched: + return False + if check & S_IXANY: + check -= S_IXANY + matched = False + if mode & stat.S_IXUSR: + if st.st_uid == uid: + matched = True + if mode & stat.S_IXGRP: + if st.st_uid == gid or st.st_uid in groups: + matched = True + if mode & stat.S_IXOTH: + matched = True + if not matched: + return False + if check: + raise NotImplementedError((orig, check)) + return True diff --git a/Tools/c-analyzer/c_analyzer/common/__init__.py b/Tools/c-analyzer/c_common/info.py index e69de29bb2d..e69de29bb2d 100644 --- a/Tools/c-analyzer/c_analyzer/common/__init__.py +++ b/Tools/c-analyzer/c_common/info.py diff --git a/Tools/c-analyzer/c_common/iterutil.py b/Tools/c-analyzer/c_common/iterutil.py new file mode 100644 index 00000000000..6ded105304e --- /dev/null +++ b/Tools/c-analyzer/c_common/iterutil.py @@ -0,0 +1,48 @@ + +_NOT_SET = object() + + +def peek_and_iter(items): + if not items: + return None, None + items = iter(items) + try: + peeked = next(items) + except StopIteration: + return None, None + def chain(): + yield peeked + yield from items + return chain(), peeked + + +def iter_many(items, onempty=None): + if not items: + if onempty is None: + return + if not callable(onempty): + raise onEmpty + items = onempty(items) + yield from iter_many(items, onempty=None) + return + items = iter(items) + try: + first = next(items) + except StopIteration: + if onempty is None: + return + if not callable(onempty): + raise onEmpty + items = onempty(items) + yield from iter_many(items, onempty=None) + else: + try: + second = next(items) + except StopIteration: + yield first, False + return + else: + yield first, True + yield second, True + for item in items: + yield item, True diff --git a/Tools/c-analyzer/c_common/logging.py b/Tools/c-analyzer/c_common/logging.py new file mode 100644 index 00000000000..12398f7e385 --- /dev/null +++ b/Tools/c-analyzer/c_common/logging.py @@ -0,0 +1,63 @@ +import logging +import sys + + +VERBOSITY = 3 + + +# The root logger for the whole top-level package: +_logger = logging.getLogger(__name__.rpartition('.')[0]) + + +def configure_logger(logger, verbosity=VERBOSITY, *, + logfile=None, + maxlevel=logging.CRITICAL, + ): + level = max(1, # 0 disables it, so we use the next lowest. + min(maxlevel, + maxlevel - verbosity * 10)) + logger.setLevel(level) + #logger.propagate = False + + if not logger.handlers: + if logfile: + handler = logging.FileHandler(logfile) + else: + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(level) + #handler.setFormatter(logging.Formatter()) + logger.addHandler(handler) + + # In case the provided logger is in a sub-package... + if logger is not _logger: + configure_logger( + _logger, + verbosity, + logfile=logfile, + maxlevel=maxlevel, + ) + + +def hide_emit_errors(): + """Ignore errors while emitting log entries. + + Rather than printing a message desribing the error, we show nothing. + """ + # For now we simply ignore all exceptions. If we wanted to ignore + # specific ones (e.g. BrokenPipeError) then we would need to use + # a Handler subclass with a custom handleError() method. + orig = logging.raiseExceptions + logging.raiseExceptions = False + def restore(): + logging.raiseExceptions = orig + return restore + + +class Printer: + def __init__(self, verbosity=VERBOSITY): + self.verbosity = verbosity + + def info(self, *args, **kwargs): + if self.verbosity < 3: + return + print(*args, **kwargs) diff --git a/Tools/c-analyzer/c_common/misc.py b/Tools/c-analyzer/c_common/misc.py new file mode 100644 index 00000000000..bfd503ab24a --- /dev/null +++ b/Tools/c-analyzer/c_common/misc.py @@ -0,0 +1,7 @@ + +class Labeled: + __slots__ = ('_label',) + def __init__(self, label): + self._label = label + def __repr__(self): + return f'<{self._label}>' diff --git a/Tools/c-analyzer/c_common/scriptutil.py b/Tools/c-analyzer/c_common/scriptutil.py new file mode 100644 index 00000000000..939a85003b2 --- /dev/null +++ b/Tools/c-analyzer/c_common/scriptutil.py @@ -0,0 +1,577 @@ +import argparse +import contextlib +import fnmatch +import logging +import os +import os.path +import shutil +import sys + +from . import fsutil, strutil, iterutil, logging as loggingutil + + +def get_prog(spec=None, *, absolute=False, allowsuffix=True): + if spec is None: + _, spec = _find_script() + # This is more natural for prog than __file__ would be. + filename = sys.argv[0] + elif isinstance(spec, str): + filename = os.path.normpath(spec) + spec = None + else: + filename = spec.origin + if _is_standalone(filename): + # Check if "installed". + if allowsuffix or not filename.endswith('.py'): + basename = os.path.basename(filename) + found = shutil.which(basename) + if found: + script = os.path.abspath(filename) + found = os.path.abspath(found) + if os.path.normcase(script) == os.path.normcase(found): + return basename + # It is only "standalone". + if absolute: + filename = os.path.abspath(filename) + return filename + elif spec is not None: + module = spec.name + if module.endswith('.__main__'): + module = module[:-9] + return f'{sys.executable} -m {module}' + else: + if absolute: + filename = os.path.abspath(filename) + return f'{sys.executable} {filename}' + + +def _find_script(): + frame = sys._getframe(2) + while frame.f_globals['__name__'] != '__main__': + frame = frame.f_back + + # This should match sys.argv[0]. + filename = frame.f_globals['__file__'] + # This will be None if -m wasn't used.. + spec = frame.f_globals['__spec__'] + return filename, spec + + +def is_installed(filename, *, allowsuffix=True): + if not allowsuffix and filename.endswith('.py'): + return False + filename = os.path.abspath(os.path.normalize(filename)) + found = shutil.which(os.path.basename(filename)) + if not found: + return False + if found != filename: + return False + return _is_standalone(filename) + + +def is_standalone(filename): + filename = os.path.abspath(os.path.normalize(filename)) + return _is_standalone(filename) + + +def _is_standalone(filename): + return fsutil.is_executable(filename) + + +################################## +# logging + +VERBOSITY = 3 + +TRACEBACK = os.environ.get('SHOW_TRACEBACK', '').strip() +TRACEBACK = bool(TRACEBACK and TRACEBACK.upper() not in ('0', 'FALSE', 'NO')) + + +logger = logging.getLogger(__name__) + + +def configure_logger(verbosity, logger=None, **kwargs): + if logger is None: + # Configure the root logger. + logger = logging.getLogger() + loggingutil.configure_logger(logger, verbosity, **kwargs) + + +################################## +# selections + +class UnsupportedSelectionError(Exception): + def __init__(self, values, possible): + self.values = tuple(values) + self.possible = tuple(possible) + super().__init__(f'unsupported selections {self.unique}') + + @property + def unique(self): + return tuple(sorted(set(self.values))) + + +def normalize_selection(selected: str, *, possible=None): + if selected in (None, True, False): + return selected + elif isinstance(selected, str): + selected = [selected] + elif not selected: + return () + + unsupported = [] + _selected = set() + for item in selected: + if not item: + continue + for value in item.strip().replace(',', ' ').split(): + if not value: + continue + # XXX Handle subtraction (leading "-"). + if possible and value not in possible and value != 'all': + unsupported.append(value) + _selected.add(value) + if unsupported: + raise UnsupportedSelectionError(unsupported, tuple(possible)) + if 'all' in _selected: + return True + return frozenset(selected) + + +################################## +# CLI parsing helpers + +class CLIArgSpec(tuple): + def __new__(cls, *args, **kwargs): + return super().__new__(cls, (args, kwargs)) + + def __repr__(self): + args, kwargs = self + args = [repr(arg) for arg in args] + for name, value in kwargs.items(): + args.append(f'{name}={value!r}') + return f'{type(self).__name__}({", ".join(args)})' + + def __call__(self, parser, *, _noop=(lambda a: None)): + self.apply(parser) + return _noop + + def apply(self, parser): + args, kwargs = self + parser.add_argument(*args, **kwargs) + + +def apply_cli_argspecs(parser, specs): + processors = [] + for spec in specs: + if callable(spec): + procs = spec(parser) + _add_procs(processors, procs) + else: + args, kwargs = spec + parser.add_argument(args, kwargs) + return processors + + +def _add_procs(flattened, procs): + # XXX Fail on non-empty, non-callable procs? + if not procs: + return + if callable(procs): + flattened.append(procs) + else: + #processors.extend(p for p in procs if callable(p)) + for proc in procs: + _add_procs(flattened, proc) + + +def add_verbosity_cli(parser): + parser.add_argument('-q', '--quiet', action='count', default=0) + parser.add_argument('-v', '--verbose', action='count', default=0) + + def process_args(args): + ns = vars(args) + key = 'verbosity' + if key in ns: + parser.error(f'duplicate arg {key!r}') + ns[key] = max(0, VERBOSITY + ns.pop('verbose') - ns.pop('quiet')) + return key + return process_args + + +def add_traceback_cli(parser): + parser.add_argument('--traceback', '--tb', action='store_true', + default=TRACEBACK) + parser.add_argument('--no-traceback', '--no-tb', dest='traceback', + action='store_const', const=False) + + def process_args(args): + ns = vars(args) + key = 'traceback_cm' + if key in ns: + parser.error(f'duplicate arg {key!r}') + showtb = ns.pop('traceback') + + @contextlib.contextmanager + def traceback_cm(): + restore = loggingutil.hide_emit_errors() + try: + yield + except BrokenPipeError: + # It was piped to "head" or something similar. + pass + except NotImplementedError: + raise # re-raise + except Exception as exc: + if not showtb: + sys.exit(f'ERROR: {exc}') + raise # re-raise + except KeyboardInterrupt: + if not showtb: + sys.exit('\nINTERRUPTED') + raise # re-raise + except BaseException as exc: + if not showtb: + sys.exit(f'{type(exc).__name__}: {exc}') + raise # re-raise + finally: + restore() + ns[key] = traceback_cm() + return key + return process_args + + +def add_sepval_cli(parser, opt, dest, choices, *, sep=',', **kwargs): +# if opt is True: +# parser.add_argument(f'--{dest}', action='append', **kwargs) +# elif isinstance(opt, str) and opt.startswith('-'): +# parser.add_argument(opt, dest=dest, action='append', **kwargs) +# else: +# arg = dest if not opt else opt +# kwargs.setdefault('nargs', '+') +# parser.add_argument(arg, dest=dest, action='append', **kwargs) + if not isinstance(opt, str): + parser.error(f'opt must be a string, got {opt!r}') + elif opt.startswith('-'): + parser.add_argument(opt, dest=dest, action='append', **kwargs) + else: + kwargs.setdefault('nargs', '+') + #kwargs.setdefault('metavar', opt.upper()) + parser.add_argument(opt, dest=dest, action='append', **kwargs) + + def process_args(args): + ns = vars(args) + + # XXX Use normalize_selection()? + if isinstance(ns[dest], str): + ns[dest] = [ns[dest]] + selections = [] + for many in ns[dest] or (): + for value in many.split(sep): + if value not in choices: + parser.error(f'unknown {dest} {value!r}') + selections.append(value) + ns[dest] = selections + return process_args + + +def add_files_cli(parser, *, excluded=None, nargs=None): + process_files = add_file_filtering_cli(parser, excluded=excluded) + parser.add_argument('filenames', nargs=nargs or '+', metavar='FILENAME') + return [ + process_files, + ] + + +def add_file_filtering_cli(parser, *, excluded=None): + parser.add_argument('--start') + parser.add_argument('--include', action='append') + parser.add_argument('--exclude', action='append') + + excluded = tuple(excluded or ()) + + def process_args(args): + ns = vars(args) + key = 'iter_filenames' + if key in ns: + parser.error(f'duplicate arg {key!r}') + + _include = tuple(ns.pop('include') or ()) + _exclude = excluded + tuple(ns.pop('exclude') or ()) + kwargs = dict( + start=ns.pop('start'), + include=tuple(_parse_files(_include)), + exclude=tuple(_parse_files(_exclude)), + # We use the default for "show_header" + ) + ns[key] = (lambda files: fsutil.iter_filenames(files, **kwargs)) + return process_args + + +def _parse_files(filenames): + for filename, _ in strutil.parse_entries(filenames): + yield filename.strip() + + +def add_failure_filtering_cli(parser, pool, *, default=False): + parser.add_argument('--fail', action='append', + metavar=f'"{{all|{"|".join(sorted(pool))}}},..."') + parser.add_argument('--no-fail', dest='fail', action='store_const', const=()) + + def process_args(args): + ns = vars(args) + + fail = ns.pop('fail') + try: + fail = normalize_selection(fail, possible=pool) + except UnsupportedSelectionError as exc: + parser.error(f'invalid --fail values: {", ".join(exc.unique)}') + else: + if fail is None: + fail = default + + if fail is True: + def ignore_exc(_exc): + return False + elif fail is False: + def ignore_exc(_exc): + return True + else: + def ignore_exc(exc): + for err in fail: + if type(exc) == pool[err]: + return False + else: + return True + args.ignore_exc = ignore_exc + return process_args + + +def add_kind_filtering_cli(parser, *, default=None): + parser.add_argument('--kinds', action='append') + + def process_args(args): + ns = vars(args) + + kinds = [] + for kind in ns.pop('kinds') or default or (): + kinds.extend(kind.strip().replace(',', ' ').split()) + + if not kinds: + match_kind = (lambda k: True) + else: + included = set() + excluded = set() + for kind in kinds: + if kind.startswith('-'): + kind = kind[1:] + excluded.add(kind) + if kind in included: + included.remove(kind) + else: + included.add(kind) + if kind in excluded: + excluded.remove(kind) + if excluded: + if included: + ... # XXX fail? + def match_kind(kind, *, _excluded=excluded): + return kind not in _excluded + else: + def match_kind(kind, *, _included=included): + return kind in _included + args.match_kind = match_kind + return process_args + + +COMMON_CLI = [ + add_verbosity_cli, + add_traceback_cli, + #add_dryrun_cli, +] + + +def add_commands_cli(parser, commands, *, commonspecs=COMMON_CLI, subset=None): + arg_processors = {} + if isinstance(subset, str): + cmdname = subset + try: + _, argspecs, _ = commands[cmdname] + except KeyError: + raise ValueError(f'unsupported subset {subset!r}') + parser.set_defaults(cmd=cmdname) + arg_processors[cmdname] = _add_cmd_cli(parser, commonspecs, argspecs) + else: + if subset is None: + cmdnames = subset = list(commands) + elif not subset: + raise NotImplementedError + elif isinstance(subset, set): + cmdnames = [k for k in commands if k in subset] + subset = sorted(subset) + else: + cmdnames = [n for n in subset if n in commands] + if len(cmdnames) < len(subset): + bad = tuple(n for n in subset if n not in commands) + raise ValueError(f'unsupported subset {bad}') + + common = argparse.ArgumentParser(add_help=False) + common_processors = apply_cli_argspecs(common, commonspecs) + subs = parser.add_subparsers(dest='cmd') + for cmdname in cmdnames: + description, argspecs, _ = commands[cmdname] + sub = subs.add_parser( + cmdname, + description=description, + parents=[common], + ) + cmd_processors = _add_cmd_cli(sub, (), argspecs) + arg_processors[cmdname] = common_processors + cmd_processors + return arg_processors + + +def _add_cmd_cli(parser, commonspecs, argspecs): + processors = [] + argspecs = list(commonspecs or ()) + list(argspecs or ()) + for argspec in argspecs: + if callable(argspec): + procs = argspec(parser) + _add_procs(processors, procs) + else: + if not argspec: + raise NotImplementedError + args = list(argspec) + if not isinstance(args[-1], str): + kwargs = args.pop() + if not isinstance(args[0], str): + try: + args, = args + except (TypeError, ValueError): + parser.error(f'invalid cmd args {argspec!r}') + else: + kwargs = {} + parser.add_argument(*args, **kwargs) + # There will be nothing to process. + return processors + + +def _flatten_processors(processors): + for proc in processors: + if proc is None: + continue + if callable(proc): + yield proc + else: + yield from _flatten_processors(proc) + + +def process_args(args, processors, *, keys=None): + processors = _flatten_processors(processors) + ns = vars(args) + extracted = {} + if keys is None: + for process_args in processors: + for key in process_args(args): + extracted[key] = ns.pop(key) + else: + remainder = set(keys) + for process_args in processors: + hanging = process_args(args) + if isinstance(hanging, str): + hanging = [hanging] + for key in hanging or (): + if key not in remainder: + raise NotImplementedError(key) + extracted[key] = ns.pop(key) + remainder.remove(key) + if remainder: + raise NotImplementedError(sorted(remainder)) + return extracted + + +def process_args_by_key(args, processors, keys): + extracted = process_args(args, processors, keys=keys) + return [extracted[key] for key in keys] + + +################################## +# commands + +def set_command(name, add_cli): + """A decorator factory to set CLI info.""" + def decorator(func): + if hasattr(func, '__cli__'): + raise Exception(f'already set') + func.__cli__ = (name, add_cli) + return func + return decorator + + +################################## +# main() helpers + +def filter_filenames(filenames, iter_filenames=None): + for filename, check, _ in _iter_filenames(filenames, iter_filenames): + if (reason := check()): + logger.debug(f'{filename}: {reason}') + continue + yield filename + + +def main_for_filenames(filenames, iter_filenames=None): + for filename, check, show in _iter_filenames(filenames, iter_filenames): + if show: + print() + print('-------------------------------------------') + print(filename) + if (reason := check()): + print(reason) + continue + yield filename + + +def _iter_filenames(filenames, iter_files): + if iter_files is None: + iter_files = fsutil.iter_filenames + yield from iter_files(filenames) + return + + onempty = Exception('no filenames provided') + items = iter_files(filenames) + items, peeked = iterutil.peek_and_iter(items) + if not items: + raise onempty + if isinstance(peeked, str): + check = (lambda: True) + for filename, ismany in iterutil.iter_many(items, onempty): + yield filename, check, ismany + elif len(peeked) == 3: + yield from items + else: + raise NotImplementedError + + +def iter_marks(mark='.', *, group=5, groups=2, lines=10, sep=' '): + mark = mark or '' + sep = f'{mark}{sep}' if sep else mark + end = f'{mark}{os.linesep}' + div = os.linesep + perline = group * groups + perlines = perline * lines + + if perline == 1: + yield end + elif group == 1: + yield sep + + count = 1 + while True: + if count % perline == 0: + yield end + if count % perlines == 0: + yield div + elif count % group == 0: + yield sep + else: + yield mark + count += 1 diff --git a/Tools/c-analyzer/c_analyzer/parser/__init__.py b/Tools/c-analyzer/c_common/show.py index e69de29bb2d..e69de29bb2d 100644 --- a/Tools/c-analyzer/c_analyzer/parser/__init__.py +++ b/Tools/c-analyzer/c_common/show.py diff --git a/Tools/c-analyzer/c_common/strutil.py b/Tools/c-analyzer/c_common/strutil.py new file mode 100644 index 00000000000..e7535d45bbb --- /dev/null +++ b/Tools/c-analyzer/c_common/strutil.py @@ -0,0 +1,42 @@ +import logging + + +logger = logging.getLogger(__name__) + + +def unrepr(value): + raise NotImplementedError + + +def parse_entries(entries, *, ignoresep=None): + for entry in entries: + if ignoresep and ignoresep in entry: + subentries = [entry] + else: + subentries = entry.strip().replace(',', ' ').split() + for item in subentries: + if item.startswith('+'): + filename = item[1:] + try: + infile = open(filename) + except FileNotFoundError: + logger.debug(f'ignored in parse_entries(): +{filename}') + return + with infile: + # We read the entire file here to ensure the file + # gets closed sooner rather than later. Note that + # the file would stay open if this iterator is never + # exchausted. + lines = infile.read().splitlines() + for line in _iter_significant_lines(lines): + yield line, filename + else: + yield item, None + + +def _iter_significant_lines(lines): + for line in lines: + line = line.partition('#')[0] + if not line.strip(): + continue + yield line diff --git a/Tools/c-analyzer/c_common/tables.py b/Tools/c-analyzer/c_common/tables.py new file mode 100644 index 00000000000..70a230a90b6 --- /dev/null +++ b/Tools/c-analyzer/c_common/tables.py @@ -0,0 +1,213 @@ +import csv + +from . import NOT_SET, strutil, fsutil + + +EMPTY = '-' +UNKNOWN = '???' + + +def parse_markers(markers, default=None): + if markers is NOT_SET: + return default + if not markers: + return None + if type(markers) is not str: + return markers + if markers == markers[0] * len(markers): + return [markers] + return list(markers) + + +def fix_row(row, **markers): + if isinstance(row, str): + raise NotImplementedError(row) + empty = parse_markers(markers.pop('empty', ('-',))) + unknown = parse_markers(markers.pop('unknown', ('???',))) + row = (val if val else None for val in row) + if not empty: + if not unknown: + return row + return (UNKNOWN if val in unknown else val for val in row) + elif not unknown: + return (EMPTY if val in empty else val for val in row) + return (EMPTY if val in empty else (UNKNOWN if val in unknown else val) + for val in row) + + +def _fix_read_default(row): + for value in row: + yield value.strip() + + +def _fix_write_default(row, empty=''): + for value in row: + yield empty if value is None else str(value) + + +def _normalize_fix_read(fix): + if fix is None: + fix = '' + if callable(fix): + def fix_row(row): + values = fix(row) + return _fix_read_default(values) + elif isinstance(fix, str): + def fix_row(row): + values = _fix_read_default(row) + return (None if v == fix else v + for v in values) + else: + raise NotImplementedError(fix) + return fix_row + + +def _normalize_fix_write(fix, empty=''): + if fix is None: + fix = empty + if callable(fix): + def fix_row(row): + values = fix(row) + return _fix_write_default(values, empty) + elif isinstance(fix, str): + def fix_row(row): + return _fix_write_default(row, fix) + else: + raise NotImplementedError(fix) + return fix_row + + +def read_table(infile, header, *, + sep='\t', + fix=None, + _open=open, + _get_reader=csv.reader, + ): + """Yield each row of the given ???-separated (e.g. tab) file.""" + if isinstance(infile, str): + with _open(infile, newline='') as infile: + yield from read_table( + infile, + header, + sep=sep, + fix=fix, + _open=_open, + _get_reader=_get_reader, + ) + return + lines = strutil._iter_significant_lines(infile) + + # Validate the header. + if not isinstance(header, str): + header = sep.join(header) + try: + actualheader = next(lines).strip() + except StopIteration: + actualheader = '' + if actualheader != header: + raise ValueError(f'bad header {actualheader!r}') + + fix_row = _normalize_fix_read(fix) + for row in _get_reader(lines, delimiter=sep or '\t'): + yield tuple(fix_row(row)) + + +def write_table(outfile, header, rows, *, + sep='\t', + fix=None, + backup=True, + _open=open, + _get_writer=csv.writer, + ): + """Write each of the rows to the given ???-separated (e.g. tab) file.""" + if backup: + fsutil.create_backup(outfile, backup) + if isinstance(outfile, str): + with _open(outfile, 'w', newline='') as outfile: + return write_table( + outfile, + header, + rows, + sep=sep, + fix=fix, + backup=backup, + _open=_open, + _get_writer=_get_writer, + ) + + if isinstance(header, str): + header = header.split(sep or '\t') + fix_row = _normalize_fix_write(fix) + writer = _get_writer(outfile, delimiter=sep or '\t') + writer.writerow(header) + for row in rows: + writer.writerow( + tuple(fix_row(row)) + ) + + +def parse_table(entries, sep, header=None, rawsep=None, *, + default=NOT_SET, + strict=True, + ): + header, sep = _normalize_table_file_props(header, sep) + if not sep: + raise ValueError('missing "sep"') + + ncols = None + if header: + if strict: + ncols = len(header.split(sep)) + cur_file = None + for line, filename in strutil.parse_entries(entries, ignoresep=sep): + _sep = sep + if filename: + if header and cur_file != filename: + cur_file = filename + # Skip the first line if it's the header. + if line.strip() == header: + continue + else: + # We expected the header. + raise NotImplementedError((header, line)) + elif rawsep and sep not in line: + _sep = rawsep + + row = _parse_row(line, _sep, ncols, default) + if strict and not ncols: + ncols = len(row) + yield row, filename + + +def parse_row(line, sep, *, ncols=None, default=NOT_SET): + if not sep: + raise ValueError('missing "sep"') + return _parse_row(line, sep, ncols, default) + + +def _parse_row(line, sep, ncols, default): + row = tuple(v.strip() for v in line.split(sep)) + if (ncols or 0) > 0: + diff = ncols - len(row) + if diff: + if default is NOT_SET or diff < 0: + raise Exception(f'bad row (expected {ncols} columns, got {row!r})') + row += (default,) * diff + return row + + +def _normalize_table_file_props(header, sep): + if not header: + return None, sep + + if not isinstance(header, str): + if not sep: + raise NotImplementedError(header) + header = sep.join(header) + elif not sep: + for sep in ('\t', ',', ' '): + if sep in header: + break + else: + sep = None + return header, sep diff --git a/Tools/c-analyzer/c_parser/__init__.py b/Tools/c-analyzer/c_parser/__init__.py new file mode 100644 index 00000000000..39455ddbf1a --- /dev/null +++ b/Tools/c-analyzer/c_parser/__init__.py @@ -0,0 +1,46 @@ +from .parser import parse as _parse +from .preprocessor import get_preprocessor as _get_preprocessor + + +def parse_file(filename, *, + match_kind=None, + get_file_preprocessor=None, + ): + if get_file_preprocessor is None: + get_file_preprocessor = _get_preprocessor() + yield from _parse_file(filename, match_kind, get_file_preprocessor) + + +def parse_files(filenames, *, + match_kind=None, + get_file_preprocessor=None, + ): + if get_file_preprocessor is None: + get_file_preprocessor = _get_preprocessor() + for filename in filenames: + yield from _parse_file(filename, match_kind, get_file_preprocessor) + + +def _parse_file(filename, match_kind, get_file_preprocessor): + # Preprocess the file. + preprocess = get_file_preprocessor(filename) + preprocessed = preprocess() + if preprocessed is None: + return + + # Parse the lines. + srclines = ((l.file, l.data) for l in preprocessed if l.kind == 'source') + for item in _parse(srclines): + if match_kind is not None and not match_kind(item.kind): + continue + if not item.filename: + raise NotImplementedError(repr(item)) + yield item + + +def parse_signature(text): + raise NotImplementedError + + +# aliases +from .info import resolve_parsed diff --git a/Tools/c-analyzer/c_parser/__main__.py b/Tools/c-analyzer/c_parser/__main__.py new file mode 100644 index 00000000000..1752a703f60 --- /dev/null +++ b/Tools/c-analyzer/c_parser/__main__.py @@ -0,0 +1,261 @@ +import logging +import os.path +import sys + +from c_common.scriptutil import ( + CLIArgSpec as Arg, + add_verbosity_cli, + add_traceback_cli, + add_kind_filtering_cli, + add_files_cli, + add_commands_cli, + process_args_by_key, + configure_logger, + get_prog, + main_for_filenames, +) +from .preprocessor import get_preprocessor +from .preprocessor.__main__ import ( + add_common_cli as add_preprocessor_cli, +) +from .info import KIND +from . import parse_file as _iter_parsed + + +logger = logging.getLogger(__name__) + + +def _format_vartype(vartype): + if isinstance(vartype, str): + return vartype + + data = vartype + try: + vartype = data['vartype'] + except KeyError: + storage, typequal, typespec, abstract = vartype.values() + else: + storage = data.get('storage') + if storage: + _, typequal, typespec, abstract = vartype.values() + else: + storage, typequal, typespec, abstract = vartype.values() + + vartype = f'{typespec} {abstract}' + if typequal: + vartype = f'{typequal} {vartype}' + if storage: + vartype = f'{storage} {vartype}' + return vartype + + +def _get_preprocessor(filename, **kwargs): + return get_processor(filename, + log_err=print, + **kwargs + ) + + +####################################### +# the formats + +def fmt_raw(filename, item, *, showfwd=None): + yield str(tuple(item)) + + +def fmt_summary(filename, item, *, showfwd=None): + if item.filename and item.filename != os.path.join('.', filename): + yield f'> {item.filename}' + if showfwd is None: + LINE = ' {lno:>5} {kind:10} {funcname:40} {fwd:1} {name:40} {data}' + else: + LINE = ' {lno:>5} {kind:10} {funcname:40} {name:40} {data}' + lno = kind = funcname = fwd = name = data = '' + MIN_LINE = len(LINE.format(**locals())) + + fileinfo, kind, funcname, name, data = item + lno = fileinfo.lno if fileinfo and fileinfo.lno >= 0 else '' + funcname = funcname or ' --' + name = name or ' --' + isforward = False + if kind is KIND.FUNCTION: + storage, inline, params, returntype, isforward = data.values() + returntype = _format_vartype(returntype) + data = returntype + params + if inline: + data = f'inline {data}' + if storage: + data = f'{storage} {data}' + elif kind is KIND.VARIABLE: + data = _format_vartype(data) + elif kind is KIND.STRUCT or kind is KIND.UNION: + if data is None: + isforward = True + else: + fields = data + data = f'({len(data)}) {{ ' + indent = ',\n' + ' ' * (MIN_LINE + len(data)) + data += ', '.join(f.name for f in fields[:5]) + fields = fields[5:] + while fields: + data = f'{data}{indent}{", ".join(f.name for f in fields[:5])}' + fields = fields[5:] + data += ' }' + elif kind is KIND.ENUM: + if data is None: + isforward = True + else: + names = [d if isinstance(d, str) else d.name + for d in data] + data = f'({len(data)}) {{ ' + indent = ',\n' + ' ' * (MIN_LINE + len(data)) + data += ', '.join(names[:5]) + names = names[5:] + while names: + data = f'{data}{indent}{", ".join(names[:5])}' + names = names[5:] + data += ' }' + elif kind is KIND.TYPEDEF: + data = f'typedef {data}' + elif kind == KIND.STATEMENT: + pass + else: + raise NotImplementedError(item) + if isforward: + fwd = '*' + if not showfwd and showfwd is not None: + return + elif showfwd: + return + kind = kind.value + yield LINE.format(**locals()) + + +def fmt_full(filename, item, *, showfwd=None): + raise NotImplementedError + + +FORMATS = { + 'raw': fmt_raw, + 'summary': fmt_summary, + 'full': fmt_full, +} + + +def add_output_cli(parser): + parser.add_argument('--format', dest='fmt', default='summary', choices=tuple(FORMATS)) + parser.add_argument('--showfwd', action='store_true', default=None) + parser.add_argument('--no-showfwd', dest='showfwd', action='store_false', default=None) + + def process_args(args): + pass + return process_args + + +####################################### +# the commands + +def _cli_parse(parser, excluded=None, **prepr_kwargs): + process_output = add_output_cli(parser) + process_kinds = add_kind_filtering_cli(parser) + process_preprocessor = add_preprocessor_cli(parser, **prepr_kwargs) + process_files = add_files_cli(parser, excluded=excluded) + return [ + process_output, + process_kinds, + process_preprocessor, + process_files, + ] + + +def cmd_parse(filenames, *, + fmt='summary', + showfwd=None, + iter_filenames=None, + **kwargs + ): + if 'get_file_preprocessor' not in kwargs: + kwargs['get_file_preprocessor'] = _get_preprocessor() + try: + do_fmt = FORMATS[fmt] + except KeyError: + raise ValueError(f'unsupported fmt {fmt!r}') + for filename in main_for_filenames(filenames, iter_filenames): + for item in _iter_parsed(filename, **kwargs): + for line in do_fmt(filename, item, showfwd=showfwd): + print(line) + + +def _cli_data(parser): + ... + + return [] + + +def cmd_data(filenames, + **kwargs + ): + # XXX + raise NotImplementedError + + +COMMANDS = { + 'parse': ( + 'parse the given C source & header files', + [_cli_parse], + cmd_parse, + ), + 'data': ( + 'check/manage local data (e.g. excludes, macros)', + [_cli_data], + cmd_data, + ), +} + + +####################################### +# the script + +def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *, subset='parse'): + import argparse + parser = argparse.ArgumentParser( + prog=prog or get_prog, + ) + + processors = add_commands_cli( + parser, + commands={k: v[1] for k, v in COMMANDS.items()}, + commonspecs=[ + add_verbosity_cli, + add_traceback_cli, + ], + subset=subset, + ) + + args = parser.parse_args(argv) + ns = vars(args) + + cmd = ns.pop('cmd') + + verbosity, traceback_cm = process_args_by_key( + args, + processors[cmd], + ['verbosity', 'traceback_cm'], + ) + + return cmd, ns, verbosity, traceback_cm + + +def main(cmd, cmd_kwargs): + try: + run_cmd = COMMANDS[cmd][0] + except KeyError: + raise ValueError(f'unsupported cmd {cmd!r}') + run_cmd(**cmd_kwargs) + + +if __name__ == '__main__': + cmd, cmd_kwargs, verbosity, traceback_cm = parse_args() + configure_logger(verbosity) + with traceback_cm: + main(cmd, cmd_kwargs) diff --git a/Tools/c-analyzer/c_parser/_state_machine.py b/Tools/c-analyzer/c_parser/_state_machine.py new file mode 100644 index 00000000000..b505b4e3e47 --- /dev/null +++ b/Tools/c-analyzer/c_parser/_state_machine.py @@ -0,0 +1,244 @@ + +f''' + struct {ANON_IDENTIFIER}; + struct {{ ... }} + struct {IDENTIFIER} {{ ... }} + + union {ANON_IDENTIFIER}; + union {{ ... }} + union {IDENTIFIER} {{ ... }} + + enum {ANON_IDENTIFIER}; + enum {{ ... }} + enum {IDENTIFIER} {{ ... }} + + typedef {VARTYPE} {IDENTIFIER}; + typedef {IDENTIFIER}; + typedef {IDENTIFIER}; + typedef {IDENTIFIER}; +''' + + +def parse(srclines): + if isinstance(srclines, str): # a filename + raise NotImplementedError + + + +# This only handles at most 10 nested levels. +#MATCHED_PARENS = textwrap.dedent(rf''' +# # matched parens +# (?: +# [(] # level 0 +# (?: +# [^()]* +# [(] # level 1 +# (?: +# [^()]* +# [(] # level 2 +# (?: +# [^()]* +# [(] # level 3 +# (?: +# [^()]* +# [(] # level 4 +# (?: +# [^()]* +# [(] # level 5 +# (?: +# [^()]* +# [(] # level 6 +# (?: +# [^()]* +# [(] # level 7 +# (?: +# [^()]* +# [(] # level 8 +# (?: +# [^()]* +# [(] # level 9 +# (?: +# [^()]* +# [(] # level 10 +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# )* +# [^()]* +# [)] +# ) +# # end matched parens +# ''') + +''' + # for loop + (?: + \s* \b for + \s* [(] + ( + [^;]* ; + [^;]* ; + .*? + ) # <header> + [)] + \s* + (?: + (?: + ( + {_ind(SIMPLE_STMT, 6)} + ) # <stmt> + ; + ) + | + ( {{ ) # <open> + ) + ) + | + + + + ( + (?: + (?: + (?: + {_ind(SIMPLE_STMT, 6)} + )? + return \b \s* + {_ind(INITIALIZER, 5)} + ) + | + (?: + (?: + {IDENTIFIER} \s* + (?: . | -> ) \s* + )* + {IDENTIFIER} + \s* = \s* + {_ind(INITIALIZER, 5)} + ) + | + (?: + {_ind(SIMPLE_STMT, 5)} + ) + ) + | + # cast compound literal + (?: + (?: + [^'"{{}};]* + {_ind(STRING_LITERAL, 5)} + )* + [^'"{{}};]*? + [^'"{{}};=] + = + \s* [(] [^)]* [)] + \s* {{ [^;]* }} + ) + ) # <stmt> + + + + # compound statement + (?: + ( + (?: + + # "for" statements are handled separately above. + (?: (?: else \s+ )? if | switch | while ) \s* + {_ind(COMPOUND_HEAD, 5)} + ) + | + (?: else | do ) + # We do not worry about compound statements for labels, + # "case", or "default". + )? # <header> + \s* + ( {{ ) # <open> + ) + + + + ( + (?: + [^'"{{}};]* + {_ind(STRING_LITERAL, 5)} + )* + [^'"{{}};]* + # Presumably we will not see "== {{". + [^\s='"{{}};] + )? # <header> + + + + ( + \b + (?: + # We don't worry about labels with a compound statement. + (?: + switch \s* [(] [^{{]* [)] + ) + | + (?: + case \b \s* [^:]+ [:] + ) + | + (?: + default \s* [:] + ) + | + (?: + do + ) + | + (?: + while \s* [(] [^{{]* [)] + ) + | + #(?: + # for \s* [(] [^{{]* [)] + # ) + #| + (?: + if \s* [(] + (?: [^{{]* [^)] \s* {{ )* [^{{]* + [)] + ) + | + (?: + else + (?: + \s* + if \s* [(] + (?: [^{{]* [^)] \s* {{ )* [^{{]* + [)] + )? + ) + ) + )? # <header> +''' diff --git a/Tools/c-analyzer/c_parser/datafiles.py b/Tools/c-analyzer/c_parser/datafiles.py new file mode 100644 index 00000000000..5bdb946b177 --- /dev/null +++ b/Tools/c-analyzer/c_parser/datafiles.py @@ -0,0 +1,150 @@ +import os.path + +import c_common.tables as _tables +import c_parser.info as _info + + +BASE_COLUMNS = [ + 'filename', + 'funcname', + 'name', + 'kind', +] +END_COLUMNS = { + 'parsed': 'data', + 'decls': 'declaration', +} + + +def _get_columns(group, extra=None): + return BASE_COLUMNS + list(extra or ()) + [END_COLUMNS[group]] + #return [ + # *BASE_COLUMNS, + # *extra or (), + # END_COLUMNS[group], + #] + + +############################# +# high-level + +def read_parsed(infile): + # XXX Support other formats than TSV? + columns = _get_columns('parsed') + for row in _tables.read_table(infile, columns, sep='\t', fix='-'): + yield _info.ParsedItem.from_row(row, columns) + + +def write_parsed(items, outfile): + # XXX Support other formats than TSV? + columns = _get_columns('parsed') + rows = (item.as_row(columns) for item in items) + _tables.write_table(outfile, columns, rows, sep='\t', fix='-') + + +def read_decls(infile, fmt=None): + if fmt is None: + fmt = _get_format(infile) + read_all, _ = _get_format_handlers('decls', fmt) + for decl, _ in read_all(infile): + yield decl + + +def write_decls(decls, outfile, fmt=None, *, backup=False): + if fmt is None: + fmt = _get_format(infile) + _, write_all = _get_format_handlers('decls', fmt) + write_all(decls, outfile, backup=backup) + + +############################# +# formats + +def _get_format(file, default='tsv'): + if isinstance(file, str): + filename = file + else: + filename = getattr(file, 'name', '') + _, ext = os.path.splitext(filename) + return ext[1:] if ext else default + + +def _get_format_handlers(group, fmt): + # XXX Use a registry. + if group != 'decls': + raise NotImplementedError(group) + if fmt == 'tsv': + return (_iter_decls_tsv, _write_decls_tsv) + else: + raise NotImplementedError(fmt) + + +# tsv + +def iter_decls_tsv(infile, extracolumns=None, relroot=None): + for info, extra in _iter_decls_tsv(infile, extracolumns, relroot): + decl = _info.Declaration.from_row(info) + yield decl, extra + + +def write_decls_tsv(decls, outfile, extracolumns=None, *, + relroot=None, + **kwargs + ): + # XXX Move the row rendering here. + _write_decls_tsv(rows, outfile, extracolumns, relroot, kwargs) + + +def _iter_decls_tsv(infile, extracolumns=None, relroot=None): + columns = _get_columns('decls', extracolumns) + for row in _tables.read_table(infile, columns, sep='\t'): + if extracolumns: + declinfo = row[:4] + row[-1:] + extra = row[4:-1] + else: + declinfo = row + extra = None + if relroot: + # XXX Use something like tables.fix_row() here. + declinfo = [None if v == '-' else v + for v in declinfo] + declinfo[0] = os.path.join(relroot, declinfo[0]) + yield declinfo, extra + + +def _write_decls_tsv(decls, outfile, extracolumns, relroot,kwargs): + columns = _get_columns('decls', extracolumns) + if extracolumns: + def render_decl(decl): + if type(row) is tuple: + decl, *extra = decl + else: + extra = () + extra += ('???',) * (len(extraColumns) - len(extra)) + *row, declaration = _render_known_row(decl, relroot) + row += extra + (declaration,) + return row + else: + render_decl = _render_known_decl + _tables.write_table( + outfile, + header='\t'.join(columns), + rows=(render_decl(d, relroot) for d in decls), + sep='\t', + **kwargs + ) + + +def _render_known_decl(decl, relroot, *, + # These match BASE_COLUMNS + END_COLUMNS[group]. + _columns = 'filename parent name kind data'.split(), + ): + if not isinstance(decl, _info.Declaration): + # e.g. Analyzed + decl = decl.decl + rowdata = decl.render_rowdata(_columns) + if relroot: + rowdata['filename'] = os.path.relpath(rowdata['filename'], relroot) + return [rowdata[c] or '-' for c in _columns] + # XXX + #return _tables.fix_row(rowdata[c] for c in columns) diff --git a/Tools/c-analyzer/c_parser/info.py b/Tools/c-analyzer/c_parser/info.py new file mode 100644 index 00000000000..a07ce2e0ccb --- /dev/null +++ b/Tools/c-analyzer/c_parser/info.py @@ -0,0 +1,1658 @@ +from collections import namedtuple +import enum +import os.path +import re + +from c_common.clsutil import classonly +import c_common.misc as _misc +import c_common.strutil as _strutil +import c_common.tables as _tables +from .parser._regexes import SIMPLE_TYPE + + +FIXED_TYPE = _misc.Labeled('FIXED_TYPE') + +POTS_REGEX = re.compile(rf'^{SIMPLE_TYPE}$', re.VERBOSE) + + +def is_pots(typespec): + if not typespec: + return None + if type(typespec) is not str: + _, _, _, typespec, _ = get_parsed_vartype(typespec) + return POTS_REGEX.match(typespec) is not None + + +def is_funcptr(vartype): + if not vartype: + return None + _, _, _, _, abstract = get_parsed_vartype(vartype) + return _is_funcptr(abstract) + + +def _is_funcptr(declstr): + if not declstr: + return None + # XXX Support "(<name>*)(". + return '(*)(' in declstr.replace(' ', '') + + +def is_exported_symbol(decl): + _, storage, _, _, _ = get_parsed_vartype(decl) + raise NotImplementedError + + +def is_process_global(vardecl): + kind, storage, _, _, _ = get_parsed_vartype(vardecl) + if kind is not KIND.VARIABLE: + raise NotImplementedError(vardecl) + if 'static' in (storage or ''): + return True + + if hasattr(vardecl, 'parent'): + parent = vardecl.parent + else: + parent = vardecl.get('parent') + return not parent + + +def is_fixed_type(vardecl): + if not vardecl: + return None + _, _, _, typespec, abstract = get_parsed_vartype(vardecl) + if 'typeof' in typespec: + raise NotImplementedError(vardecl) + elif not abstract: + return True + + if '*' not in abstract: + # XXX What about []? + return True + elif _is_funcptr(abstract): + return True + else: + for after in abstract.split('*')[1:]: + if not after.lstrip().startswith('const'): + return False + else: + return True + + +def is_immutable(vardecl): + if not vardecl: + return None + if not is_fixed_type(vardecl): + return False + _, _, typequal, _, _ = get_parsed_vartype(vardecl) + # If there, it can only be "const" or "volatile". + return typequal == 'const' + + +############################# +# kinds + +@enum.unique +class KIND(enum.Enum): + + # XXX Use these in the raw parser code. + TYPEDEF = 'typedef' + STRUCT = 'struct' + UNION = 'union' + ENUM = 'enum' + FUNCTION = 'function' + VARIABLE = 'variable' + STATEMENT = 'statement' + + @classonly + def _from_raw(cls, raw): + if raw is None: + return None + elif isinstance(raw, cls): + return raw + elif type(raw) is str: + # We could use cls[raw] for the upper-case form, + # but there's no need to go to the trouble. + return cls(raw.lower()) + else: + raise NotImplementedError(raw) + + @classonly + def by_priority(cls, group=None): + if group is None: + return cls._ALL_BY_PRIORITY.copy() + elif group == 'type': + return cls._TYPE_DECLS_BY_PRIORITY.copy() + elif group == 'decl': + return cls._ALL_DECLS_BY_PRIORITY.copy() + elif isinstance(group, str): + raise NotImplementedError(group) + else: + # XXX Treat group as a set of kinds & return in priority order? + raise NotImplementedError(group) + + @classonly + def is_type_decl(cls, kind): + if kind in cls.TYPES: + return True + if not isinstance(kind, cls): + raise TypeError(f'expected KIND, got {kind!r}') + return False + + @classonly + def is_decl(cls, kind): + if kind in cls.DECLS: + return True + if not isinstance(kind, cls): + raise TypeError(f'expected KIND, got {kind!r}') + return False + + @classonly + def get_group(cls, kind, *, groups=None): + if not isinstance(kind, cls): + raise TypeError(f'expected KIND, got {kind!r}') + if groups is None: + groups = ['type'] + elif not groups: + groups = () + elif isinstance(groups, str): + group = groups + if group not in cls._GROUPS: + raise ValueError(f'unsupported group {group!r}') + groups = [group] + else: + unsupported = [g for g in groups if g not in cls._GROUPS] + if unsupported: + raise ValueError(f'unsupported groups {", ".join(repr(unsupported))}') + for group in groups: + if kind in cls._GROUPS[group]: + return group + else: + return kind.value + + @classonly + def resolve_group(cls, group): + if isinstance(group, cls): + return {group} + elif isinstance(group, str): + try: + return cls._GROUPS[group].copy() + except KeyError: + raise ValueError(f'unsupported group {group!r}') + else: + resolved = set() + for gr in group: + resolve.update(cls.resolve_group(gr)) + return resolved + #return {*cls.resolve_group(g) for g in group} + + +KIND._TYPE_DECLS_BY_PRIORITY = [ + # These are in preferred order. + KIND.TYPEDEF, + KIND.STRUCT, + KIND.UNION, + KIND.ENUM, +] +KIND._ALL_DECLS_BY_PRIORITY = [ + # These are in preferred order. + *KIND._TYPE_DECLS_BY_PRIORITY, + KIND.FUNCTION, + KIND.VARIABLE, +] +KIND._ALL_BY_PRIORITY = [ + # These are in preferred order. + *KIND._ALL_DECLS_BY_PRIORITY, + KIND.STATEMENT, +] + +KIND.TYPES = frozenset(KIND._TYPE_DECLS_BY_PRIORITY) +KIND.DECLS = frozenset(KIND._ALL_DECLS_BY_PRIORITY) +KIND._GROUPS = { + 'type': KIND.TYPES, + 'decl': KIND.DECLS, +} +KIND._GROUPS.update((k.value, {k}) for k in KIND) + + +# The module-level kind-related helpers (below) deal with <item>.kind: + +def is_type_decl(kind): + # Handle ParsedItem, Declaration, etc.. + kind = getattr(kind, 'kind', kind) + return KIND.is_type_decl(kind) + + +def is_decl(kind): + # Handle ParsedItem, Declaration, etc.. + kind = getattr(kind, 'kind', kind) + return KIND.is_decl(kind) + + +def filter_by_kind(items, kind): + if kind == 'type': + kinds = KIND._TYPE_DECLS + elif kind == 'decl': + kinds = KIND._TYPE_DECLS + try: + okay = kind in KIND + except TypeError: + kinds = set(kind) + else: + kinds = {kind} if okay else set(kind) + for item in items: + if item.kind in kinds: + yield item + + +def collate_by_kind(items): + collated = {kind: [] for kind in KIND} + for item in items: + try: + collated[item.kind].append(item) + except KeyError: + raise ValueError(f'unsupported kind in {item!r}') + return collated + + +def get_kind_group(kind): + # Handle ParsedItem, Declaration, etc.. + kind = getattr(kind, 'kind', kind) + return KIND.get_group(kind) + + +def collate_by_kind_group(items): + collated = {KIND.get_group(k): [] for k in KIND} + for item in items: + group = KIND.get_group(item.kind) + collated[group].append(item) + return collated + + +############################# +# low-level + +class FileInfo(namedtuple('FileInfo', 'filename lno')): + @classmethod + def from_raw(cls, raw): + if isinstance(raw, cls): + return raw + elif isinstance(raw, tuple): + return cls(*raw) + elif not raw: + return None + elif isinstance(raw, str): + return cls(raw, -1) + else: + raise TypeError(f'unsupported "raw": {raw:!r}') + + def __str__(self): + return self.filename + + def fix_filename(self, relroot): + filename = os.path.relpath(self.filename, relroot) + return self._replace(filename=filename) + + +class SourceLine(namedtuple('Line', 'file kind data conditions')): + KINDS = ( + #'directive', # data is ... + 'source', # "data" is the line + #'comment', # "data" is the text, including comment markers + ) + + @property + def filename(self): + return self.file.filename + + @property + def lno(self): + return self.file.lno + + +class DeclID(namedtuple('DeclID', 'filename funcname name')): + """The globally-unique identifier for a declaration.""" + + @classmethod + def from_row(cls, row, **markers): + row = _tables.fix_row(row, **markers) + return cls(*row) + + def __new__(cls, filename, funcname, name): + self = super().__new__( + cls, + filename=str(filename) if filename else None, + funcname=str(funcname) if funcname else None, + name=str(name) if name else None, + ) + self._compare = tuple(v or '' for v in self) + return self + + def __hash__(self): + return super().__hash__() + + def __eq__(self, other): + try: + other = tuple(v or '' for v in other) + except TypeError: + return NotImplemented + return self._compare == other + + def __gt__(self, other): + try: + other = tuple(v or '' for v in other) + except TypeError: + return NotImplemented + return self._compare > other + + +class ParsedItem(namedtuple('ParsedItem', 'file kind parent name data')): + + @classmethod + def from_raw(cls, raw): + if isinstance(raw, cls): + return raw + elif isinstance(raw, tuple): + return cls(*raw) + else: + raise TypeError(f'unsupported "raw": {raw:!r}') + + @classmethod + def from_row(cls, row, columns=None): + if not columns: + colnames = 'filename funcname name kind data'.split() + else: + colnames = list(columns) + for i, column in enumerate(colnames): + if column == 'file': + colnames[i] = 'filename' + elif column == 'funcname': + colnames[i] = 'parent' + if len(row) != len(set(colnames)): + raise NotImplementedError(columns, row) + kwargs = {} + for column, value in zip(colnames, row): + if column == 'filename': + kwargs['file'] = FileInfo.from_raw(value) + elif column == 'kind': + kwargs['kind'] = KIND(value) + elif column in cls._fields: + kwargs[column] = value + else: + raise NotImplementedError(column) + return cls(**kwargs) + + @property + def id(self): + try: + return self._id + except AttributeError: + if self.kind is KIND.STATEMENT: + self._id = None + else: + self._id = DeclID(str(self.file), self.funcname, self.name) + return self._id + + @property + def filename(self): + if not self.file: + return None + return self.file.filename + + @property + def lno(self): + if not self.file: + return -1 + return self.file.lno + + @property + def funcname(self): + if not self.parent: + return None + if type(self.parent) is str: + return self.parent + else: + return self.parent.name + + def as_row(self, columns=None): + if not columns: + columns = self._fields + row = [] + for column in columns: + if column == 'file': + value = self.filename + elif column == 'kind': + value = self.kind.value + elif column == 'data': + value = self._render_data() + else: + value = getattr(self, column) + row.append(value) + return row + + def _render_data(self): + if not self.data: + return None + elif isinstance(self.data, str): + return self.data + else: + # XXX + raise NotImplementedError + + +def _get_vartype(data): + try: + vartype = dict(data['vartype']) + except KeyError: + vartype = dict(data) + storage = data.get('storage') + else: + storage = data.get('storage') or vartype.get('storage') + del vartype['storage'] + return storage, vartype + + +def get_parsed_vartype(decl): + kind = getattr(decl, 'kind', None) + if isinstance(decl, ParsedItem): + storage, vartype = _get_vartype(decl.data) + typequal = vartype['typequal'] + typespec = vartype['typespec'] + abstract = vartype['abstract'] + elif isinstance(decl, dict): + kind = decl.get('kind') + storage, vartype = _get_vartype(decl) + typequal = vartype['typequal'] + typespec = vartype['typespec'] + abstract = vartype['abstract'] + elif isinstance(decl, VarType): + storage = None + typequal, typespec, abstract = decl + elif isinstance(decl, TypeDef): + storage = None + typequal, typespec, abstract = decl.vartype + elif isinstance(decl, Variable): + storage = decl.storage + typequal, typespec, abstract = decl.vartype + elif isinstance(decl, Function): + storage = decl.storage + typequal, typespec, abstract = decl.signature.returntype + elif isinstance(decl, str): + vartype, storage = VarType.from_str(decl) + typequal, typespec, abstract = vartype + else: + raise NotImplementedError(decl) + return kind, storage, typequal, typespec, abstract + + +############################# +# high-level + +class HighlevelParsedItem: + + kind = None + + FIELDS = ('file', 'parent', 'name', 'data') + + @classmethod + def from_parsed(cls, parsed): + if parsed.kind is not cls.kind: + raise TypeError(f'kind mismatch ({parsed.kind.value} != {cls.kind.value})') + data, extra = cls._resolve_data(parsed.data) + self = cls( + cls._resolve_file(parsed), + parsed.name, + data, + cls._resolve_parent(parsed) if parsed.parent else None, + **extra or {} + ) + self._parsed = parsed + return self + + @classmethod + def _resolve_file(cls, parsed): + fileinfo = FileInfo.from_raw(parsed.file) + if not fileinfo: + raise NotImplementedError(parsed) + return fileinfo + + @classmethod + def _resolve_data(cls, data): + return data, None + + @classmethod + def _raw_data(cls, data, extra): + if isinstance(data, str): + return data + else: + raise NotImplementedError(data) + + @classmethod + def _data_as_row(cls, data, extra, colnames): + row = {} + for colname in colnames: + if colname in row: + continue + rendered = cls._render_data_row_item(colname, data, extra) + if rendered is iter(rendered): + rendered, = rendered + row[colname] = rendered + return row + + @classmethod + def _render_data_row_item(cls, colname, data, extra): + if colname == 'data': + return str(data) + else: + return None + + @classmethod + def _render_data_row(cls, fmt, data, extra, colnames): + if fmt != 'row': + raise NotImplementedError + datarow = cls._data_as_row(data, extra, colnames) + unresolved = [c for c, v in datarow.items() if v is None] + if unresolved: + raise NotImplementedError(unresolved) + for colname, value in datarow.items(): + if type(value) != str: + if colname == 'kind': + datarow[colname] = value.value + else: + datarow[colname] = str(value) + return datarow + + @classmethod + def _render_data(cls, fmt, data, extra): + row = cls._render_data_row(fmt, data, extra, ['data']) + yield ' '.join(row.values()) + + @classmethod + def _resolve_parent(cls, parsed, *, _kind=None): + fileinfo = FileInfo(parsed.file.filename, -1) + if isinstance(parsed.parent, str): + if parsed.parent.isidentifier(): + name = parsed.parent + else: + # XXX It could be something like "<kind> <name>". + raise NotImplementedError(repr(parsed.parent)) + parent = ParsedItem(fileinfo, _kind, None, name, None) + elif type(parsed.parent) is tuple: + # XXX It could be something like (kind, name). + raise NotImplementedError(repr(parsed.parent)) + else: + return parsed.parent + Parent = KIND_CLASSES.get(_kind, Declaration) + return Parent.from_parsed(parent) + + @classmethod + def _parse_columns(cls, columns): + colnames = {} # {requested -> actual} + columns = list(columns or cls.FIELDS) + datacolumns = [] + for i, colname in enumerate(columns): + if colname == 'file': + columns[i] = 'filename' + colnames['file'] = 'filename' + elif colname == 'lno': + columns[i] = 'line' + colnames['lno'] = 'line' + elif colname in ('filename', 'line'): + colnames[colname] = colname + elif colname == 'data': + datacolumns.append(colname) + colnames[colname] = None + elif colname in cls.FIELDS or colname == 'kind': + colnames[colname] = colname + else: + datacolumns.append(colname) + colnames[colname] = None + return columns, datacolumns, colnames + + def __init__(self, file, name, data, parent=None, *, + _extra=None, + _shortkey=None, + _key=None, + ): + self.file = file + self.parent = parent or None + self.name = name + self.data = data + self._extra = _extra or {} + self._shortkey = _shortkey + self._key = _key + + def __repr__(self): + args = [f'{n}={getattr(self, n)!r}' + for n in ['file', 'name', 'data', 'parent', *(self._extra or ())]] + return f'{type(self).__name__}({", ".join(args)})' + + def __str__(self): + try: + return self._str + except AttributeError: + self._str = next(self.render()) + return self._str + + def __getattr__(self, name): + try: + return self._extra[name] + except KeyError: + raise AttributeError(name) + + def __hash__(self): + return hash(self._key) + + def __eq__(self, other): + if isinstance(other, HighlevelParsedItem): + return self._key == other._key + elif type(other) is tuple: + return self._key == other + else: + return NotImplemented + + def __gt__(self, other): + if isinstance(other, HighlevelParsedItem): + return self._key > other._key + elif type(other) is tuple: + return self._key > other + else: + return NotImplemented + + @property + def id(self): + return self.parsed.id + + @property + def shortkey(self): + return self._shortkey + + @property + def key(self): + return self._key + + @property + def filename(self): + if not self.file: + return None + return self.file.filename + + @property + def parsed(self): + try: + return self._parsed + except AttributeError: + parent = self.parent + if parent is not None and not isinstance(parent, str): + parent = parent.name + self._parsed = ParsedItem( + self.file, + self.kind, + parent, + self.name, + self._raw_data(), + ) + return self._parsed + + def fix_filename(self, relroot): + if self.file: + self.file = self.file.fix_filename(relroot) + + def as_rowdata(self, columns=None): + columns, datacolumns, colnames = self._parse_columns(columns) + return self._as_row(colnames, datacolumns, self._data_as_row) + + def render_rowdata(self, columns=None): + columns, datacolumns, colnames = self._parse_columns(columns) + def data_as_row(data, ext, cols): + return self._render_data_row('row', data, ext, cols) + rowdata = self._as_row(colnames, datacolumns, data_as_row) + for column, value in rowdata.items(): + colname = colnames.get(column) + if not colname: + continue + if column == 'kind': + value = value.value + else: + if column == 'parent': + if self.parent: + value = f'({self.parent.kind.value} {self.parent.name})' + if not value: + value = '-' + elif type(value) is VarType: + value = repr(str(value)) + else: + value = str(value) + rowdata[column] = value + return rowdata + + def _as_row(self, colnames, datacolumns, data_as_row): + try: + data = data_as_row(self.data, self._extra, datacolumns) + except NotImplementedError: + data = None + row = data or {} + for column, colname in colnames.items(): + if colname == 'filename': + value = self.file.filename if self.file else None + elif colname == 'line': + value = self.file.lno if self.file else None + elif colname is None: + value = getattr(self, column, None) + else: + value = getattr(self, colname, None) + row.setdefault(column, value) + return row + + def render(self, fmt='line'): + fmt = fmt or 'line' + try: + render = _FORMATS[fmt] + except KeyError: + raise TypeError(f'unsupported fmt {fmt!r}') + try: + data = self._render_data(fmt, self.data, self._extra) + except NotImplementedError: + data = '-' + yield from render(self, data) + + +### formats ### + +def _fmt_line(parsed, data=None): + parts = [ + f'<{parsed.kind.value}>', + ] + parent = '' + if parsed.parent: + parent = parsed.parent + if not isinstance(parent, str): + if parent.kind is KIND.FUNCTION: + parent = f'{parent.name}()' + else: + parent = parent.name + name = f'<{parent}>.{parsed.name}' + else: + name = parsed.name + if data is None: + data = parsed.data + elif data is iter(data): + data, = data + parts.extend([ + name, + f'<{data}>' if data else '-', + f'({str(parsed.file or "<unknown file>")})', + ]) + yield '\t'.join(parts) + + +def _fmt_full(parsed, data=None): + if parsed.kind is KIND.VARIABLE and parsed.parent: + prefix = 'local ' + suffix = f' ({parsed.parent.name})' + else: + # XXX Show other prefixes (e.g. global, public) + prefix = suffix = '' + yield f'{prefix}{parsed.kind.value} {parsed.name!r}{suffix}' + for column, info in parsed.render_rowdata().items(): + if column == 'kind': + continue + if column == 'name': + continue + if column == 'parent' and parsed.kind is not KIND.VARIABLE: + continue + if column == 'data': + if parsed.kind in (KIND.STRUCT, KIND.UNION): + column = 'members' + elif parsed.kind is KIND.ENUM: + column = 'enumerators' + elif parsed.kind is KIND.STATEMENT: + column = 'text' + data, = data + else: + column = 'signature' + data, = data + if not data: +# yield f'\t{column}:\t-' + continue + elif isinstance(data, str): + yield f'\t{column}:\t{data!r}' + else: + yield f'\t{column}:' + for line in data: + yield f'\t\t- {line}' + else: + yield f'\t{column}:\t{info}' + + +_FORMATS = { + 'raw': (lambda v, _d: [repr(v)]), + 'brief': _fmt_line, + 'line': _fmt_line, + 'full': _fmt_full, +} + + +### declarations ## + +class Declaration(HighlevelParsedItem): + + @classmethod + def from_row(cls, row, **markers): + fixed = tuple(_tables.fix_row(row, **markers)) + if cls is Declaration: + _, _, _, kind, _ = fixed + sub = KIND_CLASSES.get(KIND(kind)) + if not sub or not issubclass(sub, Declaration): + raise TypeError(f'unsupported kind, got {row!r}') + else: + sub = cls + return sub._from_row(fixed) + + @classmethod + def _from_row(cls, row): + filename, funcname, name, kind, data = row + kind = KIND._from_raw(kind) + if kind is not cls.kind: + raise TypeError(f'expected kind {cls.kind.value!r}, got {row!r}') + fileinfo = FileInfo.from_raw(filename) + if isinstance(data, str): + data, extra = cls._parse_data(data, fmt='row') + if extra: + return cls(fileinfo, name, data, funcname, _extra=extra) + else: + return cls(fileinfo, name, data, funcname) + + @classmethod + def _resolve_parent(cls, parsed, *, _kind=None): + if _kind is None: + raise TypeError(f'{cls.kind.value} declarations do not have parents ({parsed})') + return super()._resolve_parent(parsed, _kind=_kind) + + @classmethod + def _render_data(cls, fmt, data, extra): + if not data: + # XXX There should be some! Forward? + yield '???' + else: + yield from cls._format_data(fmt, data, extra) + + @classmethod + def _render_data_row_item(cls, colname, data, extra): + if colname == 'data': + return cls._format_data('row', data, extra) + else: + return None + + @classmethod + def _format_data(cls, fmt, data, extra): + raise NotImplementedError(fmt) + + @classmethod + def _parse_data(cls, datastr, fmt=None): + """This is the reverse of _render_data.""" + if not datastr or datastr is _tables.UNKNOWN or datastr == '???': + return None, None + elif datastr is _tables.EMPTY or datastr == '-': + # All the kinds have *something* even it is unknown. + raise TypeError('all declarations have data of some sort, got none') + else: + return cls._unformat_data(datastr, fmt) + + @classmethod + def _unformat_data(cls, datastr, fmt=None): + raise NotImplementedError(fmt) + + +class VarType(namedtuple('VarType', 'typequal typespec abstract')): + + @classmethod + def from_str(cls, text): + orig = text + storage, sep, text = text.strip().partition(' ') + if not sep: + text = storage + storage = None + elif storage not in ('auto', 'register', 'static', 'extern'): + text = orig + storage = None + return cls._from_str(text), storage + + @classmethod + def _from_str(cls, text): + orig = text + if text.startswith(('const ', 'volatile ')): + typequal, _, text = text.partition(' ') + else: + typequal = None + + # Extract a series of identifiers/keywords. + m = re.match(r"^ *'?([a-zA-Z_]\w*(?:\s+[a-zA-Z_]\w*)*)\s*(.*?)'?\s*$", text) + if not m: + raise ValueError(f'invalid vartype text {orig!r}') + typespec, abstract = m.groups() + + return cls(typequal, typespec, abstract or None) + + def __str__(self): + parts = [] + if self.qualifier: + parts.append(self.qualifier) + parts.append(self.spec + (self.abstract or '')) + return ' '.join(parts) + + @property + def qualifier(self): + return self.typequal + + @property + def spec(self): + return self.typespec + + +class Variable(Declaration): + kind = KIND.VARIABLE + + @classmethod + def _resolve_parent(cls, parsed): + return super()._resolve_parent(parsed, _kind=KIND.FUNCTION) + + @classmethod + def _resolve_data(cls, data): + if not data: + return None, None + storage, vartype = _get_vartype(data) + return VarType(**vartype), {'storage': storage} + + @classmethod + def _raw_data(self, data, extra): + vartype = data._asdict() + return { + 'storage': extra['storage'], + 'vartype': vartype, + } + + @classmethod + def _format_data(cls, fmt, data, extra): + storage = extra.get('storage') + text = f'{storage} {data}' if storage else str(data) + if fmt in ('line', 'brief'): + yield text + #elif fmt == 'full': + elif fmt == 'row': + yield text + else: + raise NotImplementedError(fmt) + + @classmethod + def _unformat_data(cls, datastr, fmt=None): + if fmt in ('line', 'brief'): + vartype, storage = VarType.from_str(datastr) + return vartype, {'storage': storage} + #elif fmt == 'full': + elif fmt == 'row': + vartype, storage = VarType.from_str(datastr) + return vartype, {'storage': storage} + else: + raise NotImplementedError(fmt) + + def __init__(self, file, name, data, parent=None, storage=None): + super().__init__(file, name, data, parent, + _extra={'storage': storage}, + _shortkey=f'({parent.name}).{name}' if parent else name, + _key=(str(file), + # Tilde comes after all other ascii characters. + f'~{parent or ""}~', + name, + ), + ) + + @property + def vartype(self): + return self.data + + +class Signature(namedtuple('Signature', 'params returntype inline isforward')): + + @classmethod + def from_str(cls, text): + orig = text + storage, sep, text = text.strip().partition(' ') + if not sep: + text = storage + storage = None + elif storage not in ('auto', 'register', 'static', 'extern'): + text = orig + storage = None + return cls._from_str(text), storage + + @classmethod + def _from_str(cls, text): + orig = text + inline, sep, text = text.partition('|') + if not sep: + text = inline + inline = None + + isforward = False + if text.endswith(';'): + text = text[:-1] + isforward = True + elif text.endswith('{}'): + text = text[:-2] + + index = text.rindex('(') + if index < 0: + raise ValueError(f'bad signature text {orig!r}') + params = text[index:] + while params.count('(') <= params.count(')'): + index = text.rindex('(', 0, index) + if index < 0: + raise ValueError(f'bad signature text {orig!r}') + params = text[index:] + text = text[:index] + + returntype = VarType._from_str(text.rstrip()) + + return cls(params, returntype, inline, isforward) + + def __str__(self): + parts = [] + if self.inline: + parts.extend([ + self.inline, + '|', + ]) + parts.extend([ + str(self.returntype), + self.params, + ';' if self.isforward else '{}', + ]) + return ' '.join(parts) + + @property + def returns(self): + return self.returntype + + +class Function(Declaration): + kind = KIND.FUNCTION + + @classmethod + def _resolve_data(cls, data): + if not data: + return None, None + kwargs = dict(data) + returntype = dict(data['returntype']) + del returntype['storage'] + kwargs['returntype'] = VarType(**returntype) + storage = kwargs.pop('storage') + return Signature(**kwargs), {'storage': storage} + + @classmethod + def _raw_data(self, data): + # XXX finsh! + return data + + @classmethod + def _format_data(cls, fmt, data, extra): + storage = extra.get('storage') + text = f'{storage} {data}' if storage else str(data) + if fmt in ('line', 'brief'): + yield text + #elif fmt == 'full': + elif fmt == 'row': + yield text + else: + raise NotImplementedError(fmt) + + @classmethod + def _unformat_data(cls, datastr, fmt=None): + if fmt in ('line', 'brief'): + sig, storage = Signature.from_str(sig) + return sig, {'storage': storage} + #elif fmt == 'full': + elif fmt == 'row': + sig, storage = Signature.from_str(sig) + return sig, {'storage': storage} + else: + raise NotImplementedError(fmt) + + def __init__(self, file, name, data, parent=None, storage=None): + super().__init__(file, name, data, parent, _extra={'storage': storage}) + self._shortkey = f'~{name}~ {self.data}' + self._key = ( + str(file), + self._shortkey, + ) + + @property + def signature(self): + return self.data + + +class TypeDeclaration(Declaration): + + def __init__(self, file, name, data, parent=None, *, _shortkey=None): + if not _shortkey: + _shortkey = f'{self.kind.value} {name}' + super().__init__(file, name, data, parent, + _shortkey=_shortkey, + _key=( + str(file), + _shortkey, + ), + ) + + +class POTSType(TypeDeclaration): + + def __init__(self, name): + _file = _data = _parent = None + super().__init__(_file, name, _data, _parent, _shortkey=name) + + +class FuncPtr(TypeDeclaration): + + def __init__(self, vartype): + _file = _name = _parent = None + data = vartype + self.vartype = vartype + super().__init__(_file, _name, data, _parent, _shortkey=f'<{vartype}>') + + +class TypeDef(TypeDeclaration): + kind = KIND.TYPEDEF + + @classmethod + def _resolve_data(cls, data): + if not data: + raise NotImplementedError(data) + vartype = dict(data) + del vartype['storage'] + return VarType(**vartype), None + + @classmethod + def _raw_data(self, data): + # XXX finish! + return data + + @classmethod + def _format_data(cls, fmt, data, extra): + text = str(data) + if fmt in ('line', 'brief'): + yield text + elif fmt == 'full': + yield text + elif fmt == 'row': + yield text + else: + raise NotImplementedError(fmt) + + @classmethod + def _unformat_data(cls, datastr, fmt=None): + if fmt in ('line', 'brief'): + vartype, _ = VarType.from_str(datastr) + return vartype, None + #elif fmt == 'full': + elif fmt == 'row': + vartype, _ = VarType.from_str(datastr) + return vartype, None + else: + raise NotImplementedError(fmt) + + def __init__(self, file, name, data, parent=None): + super().__init__(file, name, data, parent, _shortkey=name) + + @property + def vartype(self): + return self.data + + +class Member(namedtuple('Member', 'name vartype size')): + + @classmethod + def from_data(cls, raw, index): + name = raw.name if raw.name else index + vartype = size = None + if type(raw.data) is int: + size = raw.data + elif isinstance(raw.data, str): + size = int(raw.data) + elif raw.data: + vartype = dict(raw.data) + del vartype['storage'] + if 'size' in vartype: + size = int(vartype.pop('size')) + vartype = VarType(**vartype) + return cls(name, vartype, size) + + @classmethod + def from_str(cls, text): + name, _, vartype = text.partition(': ') + if name.startswith('#'): + name = int(name[1:]) + if vartype.isdigit(): + size = int(vartype) + vartype = None + else: + vartype, _ = VarType.from_str(vartype) + size = None + return cls(name, vartype, size) + + def __str__(self): + name = self.name if isinstance(self.name, str) else f'#{self.name}' + return f'{name}: {self.vartype or self.size}' + + +class _StructUnion(TypeDeclaration): + + @classmethod + def _resolve_data(cls, data): + if not data: + # XXX There should be some! Forward? + return None, None + return [Member.from_data(v, i) for i, v in enumerate(data)], None + + @classmethod + def _raw_data(self, data): + # XXX finish! + return data + + @classmethod + def _format_data(cls, fmt, data, extra): + if fmt in ('line', 'brief'): + members = ', '.join(f'<{m}>' for m in data) + yield f'[{members}]' + elif fmt == 'full': + for member in data: + yield f'{member}' + elif fmt == 'row': + members = ', '.join(f'<{m}>' for m in data) + yield f'[{members}]' + else: + raise NotImplementedError(fmt) + + @classmethod + def _unformat_data(cls, datastr, fmt=None): + if fmt in ('line', 'brief'): + members = [Member.from_str(m[1:-1]) + for m in datastr[1:-1].split(', ')] + return members, None + #elif fmt == 'full': + elif fmt == 'row': + members = [Member.from_str(m.rstrip('>').lstrip('<')) + for m in datastr[1:-1].split('>, <')] + return members, None + else: + raise NotImplementedError(fmt) + + def __init__(self, file, name, data, parent=None): + super().__init__(file, name, data, parent) + + @property + def members(self): + return self.data + + +class Struct(_StructUnion): + kind = KIND.STRUCT + + +class Union(_StructUnion): + kind = KIND.UNION + + +class Enum(TypeDeclaration): + kind = KIND.ENUM + + @classmethod + def _resolve_data(cls, data): + if not data: + # XXX There should be some! Forward? + return None, None + enumerators = [e if isinstance(e, str) else e.name + for e in data] + return enumerators, None + + @classmethod + def _raw_data(self, data): + # XXX finsih! + return data + + @classmethod + def _format_data(cls, fmt, data, extra): + if fmt in ('line', 'brief'): + yield repr(data) + elif fmt == 'full': + for enumerator in data: + yield f'{enumerator}' + elif fmt == 'row': + # XXX This won't work with CSV... + yield ','.join(data) + else: + raise NotImplementedError(fmt) + + @classmethod + def _unformat_data(cls, datastr, fmt=None): + if fmt in ('line', 'brief'): + return _strutil.unrepr(datastr), None + #elif fmt == 'full': + elif fmt == 'row': + return datastr.split(','), None + else: + raise NotImplementedError(fmt) + + def __init__(self, file, name, data, parent=None): + super().__init__(file, name, data, parent) + + @property + def enumerators(self): + return self.data + + +### statements ### + +class Statement(HighlevelParsedItem): + kind = KIND.STATEMENT + + @classmethod + def _resolve_data(cls, data): + # XXX finsih! + return data, None + + @classmethod + def _raw_data(self, data): + # XXX finsih! + return data + + @classmethod + def _render_data(cls, fmt, data, extra): + # XXX Handle other formats? + return repr(data) + + @classmethod + def _parse_data(self, datastr, fmt=None): + # XXX Handle other formats? + return _strutil.unrepr(datastr), None + + def __init__(self, file, name, data, parent=None): + super().__init__(file, name, data, parent, + _shortkey=data or '', + _key=( + str(file), + file.lno, + # XXX Only one stmt per line? + ), + ) + + @property + def text(self): + return self.data + + +### + +KIND_CLASSES = {cls.kind: cls for cls in [ + Variable, + Function, + TypeDef, + Struct, + Union, + Enum, + Statement, +]} + + +def resolve_parsed(parsed): + if isinstance(parsed, HighlevelParsedItem): + return parsed + try: + cls = KIND_CLASSES[parsed.kind] + except KeyError: + raise ValueError(f'unsupported kind in {parsed!r}') + return cls.from_parsed(parsed) + + +############################# +# composite + +class Declarations: + + @classmethod + def from_decls(cls, decls): + return cls(decls) + + @classmethod + def from_parsed(cls, items): + decls = (resolve_parsed(item) + for item in items + if item.kind is not KIND.STATEMENT) + return cls.from_decls(decls) + + @classmethod + def _resolve_key(cls, raw): + if isinstance(raw, str): + raw = [raw] + elif isinstance(raw, Declaration): + raw = ( + raw.filename if cls._is_public(raw) else None, + # `raw.parent` is always None for types and functions. + raw.parent if raw.kind is KIND.VARIABLE else None, + raw.name, + ) + + extra = None + if len(raw) == 1: + name, = raw + if name: + name = str(name) + if name.endswith(('.c', '.h')): + # This is only legit as a query. + key = (name, None, None) + else: + key = (None, None, name) + else: + key = (None, None, None) + elif len(raw) == 2: + parent, name = raw + name = str(name) + if isinstance(parent, Declaration): + key = (None, parent.name, name) + elif not parent: + key = (None, None, name) + else: + parent = str(parent) + if parent.endswith(('.c', '.h')): + key = (parent, None, name) + else: + key = (None, parent, name) + else: + key, extra = raw[:3], raw[3:] + filename, funcname, name = key + filename = str(filename) if filename else None + if isinstance(funcname, Declaration): + funcname = funcname.name + else: + funcname = str(funcname) if funcname else None + name = str(name) if name else None + key = (filename, funcname, name) + return key, extra + + @classmethod + def _is_public(cls, decl): + # For .c files don't we need info from .h files to make this decision? + # XXX Check for "extern". + # For now we treat all decls a "private" (have filename set). + return False + + def __init__(self, decls): + # (file, func, name) -> decl + # "public": + # * (None, None, name) + # "private", "global": + # * (file, None, name) + # "private", "local": + # * (file, func, name) + if hasattr(decls, 'items'): + self._decls = decls + else: + self._decls = {} + self._extend(decls) + + # XXX always validate? + + def validate(self): + for key, decl in self._decls.items(): + if type(key) is not tuple or len(key) != 3: + raise ValueError(f'expected 3-tuple key, got {key!r} (for decl {decl!r})') + filename, funcname, name = key + if not name: + raise ValueError(f'expected name in key, got {key!r} (for decl {decl!r})') + elif type(name) is not str: + raise ValueError(f'expected name in key to be str, got {key!r} (for decl {decl!r})') + # XXX Check filename type? + # XXX Check funcname type? + + if decl.kind is KIND.STATEMENT: + raise ValueError(f'expected a declaration, got {decl!r}') + + def __repr__(self): + return f'{type(self).__name__}({list(self)})' + + def __len__(self): + return len(self._decls) + + def __iter__(self): + yield from self._decls + + def __getitem__(self, key): + # XXX Be more exact for the 3-tuple case? + if type(key) not in (str, tuple): + raise KeyError(f'unsupported key {key!r}') + resolved, extra = self._resolve_key(key) + if extra: + raise KeyError(f'key must have at most 3 parts, got {key!r}') + if not resolved[2]: + raise ValueError(f'expected name in key, got {key!r}') + try: + return self._decls[resolved] + except KeyError: + if type(key) is tuple and len(key) == 3: + filename, funcname, name = key + else: + filename, funcname, name = resolved + if filename and not filename.endswith(('.c', '.h')): + raise KeyError(f'invalid filename in key {key!r}') + elif funcname and funcname.endswith(('.c', '.h')): + raise KeyError(f'invalid funcname in key {key!r}') + elif name and name.endswith(('.c', '.h')): + raise KeyError(f'invalid name in key {key!r}') + else: + raise # re-raise + + @property + def types(self): + return self._find(kind=KIND.TYPES) + + @property + def functions(self): + return self._find(None, None, None, KIND.FUNCTION) + + @property + def variables(self): + return self._find(None, None, None, KIND.VARIABLE) + + def iter_all(self): + yield from self._decls.values() + + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + + #def add_decl(self, decl, key=None): + # decl = _resolve_parsed(decl) + # self._add_decl(decl, key) + + def find(self, *key, **explicit): + if not key: + if not explicit: + return iter(self) + return self._find(**explicit) + + resolved, extra = self._resolve_key(key) + filename, funcname, name = resolved + if not extra: + kind = None + elif len(extra) == 1: + kind, = extra + else: + raise KeyError(f'key must have at most 4 parts, got {key!r}') + + implicit= {} + if filename: + implicit['filename'] = filename + if funcname: + implicit['funcname'] = funcname + if name: + implicit['name'] = name + if kind: + implicit['kind'] = kind + return self._find(**implicit, **explicit) + + def _find(self, filename=None, funcname=None, name=None, kind=None): + for decl in self._decls.values(): + if filename and decl.filename != filename: + continue + if funcname: + if decl.kind is not KIND.VARIABLE: + continue + if decl.parent.name != funcname: + continue + if name and decl.name != name: + continue + if kind: + kinds = KIND.resolve_group(kind) + if decl.kind not in kinds: + continue + yield decl + + def _add_decl(self, decl, key=None): + if key: + if type(key) not in (str, tuple): + raise NotImplementedError((key, decl)) + # Any partial key will be turned into a full key, but that + # same partial key will still match a key lookup. + resolved, _ = self._resolve_key(key) + if not resolved[2]: + raise ValueError(f'expected name in key, got {key!r}') + key = resolved + # XXX Also add with the decl-derived key if not the same? + else: + key, _ = self._resolve_key(decl) + self._decls[key] = decl + + def _extend(self, decls): + decls = iter(decls) + # Check only the first item. + for decl in decls: + if isinstance(decl, Declaration): + self._add_decl(decl) + # Add the rest without checking. + for decl in decls: + self._add_decl(decl) + elif isinstance(decl, HighlevelParsedItem): + raise NotImplementedError(decl) + else: + try: + key, decl = decl + except ValueError: + raise NotImplementedError(decl) + if not isinstance(decl, Declaration): + raise NotImplementedError(decl) + self._add_decl(decl, key) + # Add the rest without checking. + for key, decl in decls: + self._add_decl(decl, key) + # The iterator will be exhausted at this point. diff --git a/Tools/c-analyzer/c_parser/parser/__init__.py b/Tools/c-analyzer/c_parser/parser/__init__.py new file mode 100644 index 00000000000..7cb34caf09e --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/__init__.py @@ -0,0 +1,212 @@ +"""A simple non-validating parser for C99. + +The functions and regex patterns here are not entirely suitable for +validating C syntax. Please rely on a proper compiler for that. +Instead our goal here is merely matching and extracting information from +valid C code. + +Furthermore, the grammar rules for the C syntax (particularly as +described in the K&R book) actually describe a superset, of which the +full C langage is a proper subset. Here are some of the extra +conditions that must be applied when parsing C code: + +* ... + +(see: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) + +We have taken advantage of the elements of the C grammar that are used +only in a few limited contexts, mostly as delimiters. They allow us to +focus the regex patterns confidently. Here are the relevant tokens and +in which grammar rules they are used: + +separators: +* ";" + + (decl) struct/union: at end of each member decl + + (decl) declaration: at end of each (non-compound) decl + + (stmt) expr stmt: at end of each stmt + + (stmt) for: between exprs in "header" + + (stmt) goto: at end + + (stmt) continue: at end + + (stmt) break: at end + + (stmt) return: at end +* "," + + (decl) struct/union: between member declators + + (decl) param-list: between params + + (decl) enum: between enumerators + + (decl) initializer (compound): between initializers + + (expr) postfix: between func call args + + (expr) expression: between "assignment" exprs +* ":" + + (decl) struct/union: in member declators + + (stmt) label: between label and stmt + + (stmt) case: between expression and stmt + + (stmt) default: between "default" and stmt +* "=" + + (decl) delaration: between decl and initializer + + (decl) enumerator: between identifier and "initializer" + + (expr) assignment: between "var" and expr + +wrappers: +* "(...)" + + (decl) declarator (func ptr): to wrap ptr/name + + (decl) declarator (func ptr): around params + + (decl) declarator: around sub-declarator (for readability) + + (expr) postfix (func call): around args + + (expr) primary: around sub-expr + + (stmt) if: around condition + + (stmt) switch: around source expr + + (stmt) while: around condition + + (stmt) do-while: around condition + + (stmt) for: around "header" +* "{...}" + + (decl) enum: around enumerators + + (decl) func: around body + + (stmt) compound: around stmts +* "[...]" + * (decl) declarator: for arrays + * (expr) postfix: array access + +other: +* "*" + + (decl) declarator: for pointer types + + (expr) unary: for pointer deref + + +To simplify the regular expressions used here, we've takens some +shortcuts and made certain assumptions about the code we are parsing. +Some of these allow us to skip context-sensitive matching (e.g. braces) +or otherwise still match arbitrary C code unambiguously. However, in +some cases there are certain corner cases where the patterns are +ambiguous relative to arbitrary C code. However, they are still +unambiguous in the specific code we are parsing. + +Here are the cases where we've taken shortcuts or made assumptions: + +* there is no overlap syntactically between the local context (func + bodies) and the global context (other than variable decls), so we + do not need to worry about ambiguity due to the overlap: + + the global context has no expressions or statements + + the local context has no function definitions or type decls +* no "inline" type declarations (struct, union, enum) in function + parameters ~(including function pointers)~ +* no "inline" type decls in function return types +* no superflous parentheses in declarators +* var decls in for loops are always "simple" (e.g. no inline types) +* only inline struct/union/enum decls may be anonymouns (without a name) +* no function pointers in function pointer parameters +* for loop "headers" do not have curly braces (e.g. compound init) +* syntactically, variable decls do not overlap with stmts/exprs, except + in the following case: + spam (*eggs) (...) + This could be either a function pointer variable named "eggs" + or a call to a function named "spam", which returns a function + pointer that gets called. The only differentiator is the + syntax used in the "..." part. It will be comma-separated + parameters for the former and comma-separated expressions for + the latter. Thus, if we expect such decls or calls then we must + parse the decl params. +""" + +""" +TODO: +* extract CPython-specific code +* drop include injection (or only add when needed) +* track position instead of slicing "text" +* Parser class instead of the _iter_source() mess +* alt impl using a state machine (& tokenizer or split on delimiters) +""" + +from ..info import ParsedItem +from ._info import SourceInfo + + +def parse(srclines): + if isinstance(srclines, str): # a filename + raise NotImplementedError + + anon_name = anonymous_names() + for result in _parse(srclines, anon_name): + yield ParsedItem.from_raw(result) + + +# XXX Later: Add a separate function to deal with preprocessor directives +# parsed out of raw source. + + +def anonymous_names(): + counter = 1 + def anon_name(prefix='anon-'): + nonlocal counter + name = f'{prefix}{counter}' + counter += 1 + return name + return anon_name + + +############################# +# internal impl + +import logging + + +_logger = logging.getLogger(__name__) + + +def _parse(srclines, anon_name): + from ._global import parse_globals + + source = _iter_source(srclines) + #source = _iter_source(srclines, showtext=True) + for result in parse_globals(source, anon_name): + # XXX Handle blocks here insted of in parse_globals(). + yield result + + +def _iter_source(lines, *, maxtext=20_000, maxlines=700, showtext=False): + filestack = [] + allinfo = {} + # "lines" should be (fileinfo, data), as produced by the preprocessor code. + for fileinfo, line in lines: + if fileinfo.filename in filestack: + while fileinfo.filename != filestack[-1]: + filename = filestack.pop() + del allinfo[filename] + filename = fileinfo.filename + srcinfo = allinfo[filename] + else: + filename = fileinfo.filename + srcinfo = SourceInfo(filename) + filestack.append(filename) + allinfo[filename] = srcinfo + + _logger.debug(f'-> {line}') + srcinfo._add_line(line, fileinfo.lno) + if len(srcinfo.text) > maxtext: + break + if srcinfo.end - srcinfo.start > maxlines: + break + while srcinfo._used(): + yield srcinfo + if showtext: + _logger.debug(f'=> {srcinfo.text}') + else: + if not filestack: + srcinfo = SourceInfo('???') + else: + filename = filestack[-1] + srcinfo = allinfo[filename] + while srcinfo._used(): + yield srcinfo + if showtext: + _logger.debug(f'=> {srcinfo.text}') + yield srcinfo + if showtext: + _logger.debug(f'=> {srcinfo.text}') + if not srcinfo._ready: + return + # At this point either the file ended prematurely + # or there's "too much" text. + filename, lno, text = srcinfo.filename, srcinfo._start, srcinfo.text + if len(text) > 500: + text = text[:500] + '...' + raise Exception(f'unmatched text ({filename} starting at line {lno}):\n{text}') diff --git a/Tools/c-analyzer/c_parser/parser/_alt.py b/Tools/c-analyzer/c_parser/parser/_alt.py new file mode 100644 index 00000000000..05a9101b4f5 --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_alt.py @@ -0,0 +1,6 @@ + +def _parse(srclines, anon_name): + text = ' '.join(l for _, l in srclines) + + from ._delim import parse + yield from parse(text, anon_name) diff --git a/Tools/c-analyzer/c_parser/parser/_common.py b/Tools/c-analyzer/c_parser/parser/_common.py new file mode 100644 index 00000000000..40c36039f3f --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_common.py @@ -0,0 +1,115 @@ +import re + +from ._regexes import ( + _ind, + STRING_LITERAL, + VAR_DECL as _VAR_DECL, +) + + +def log_match(group, m): + from . import _logger + _logger.debug(f'matched <{group}> ({m.group(0)})') + + +############################# +# regex utils + +def set_capture_group(pattern, group, *, strict=True): + old = f'(?: # <{group}>' + if strict and f'(?: # <{group}>' not in pattern: + raise ValueError(f'{old!r} not found in pattern') + return pattern.replace(old, f'( # <{group}>', 1) + + +def set_capture_groups(pattern, groups, *, strict=True): + for group in groups: + pattern = set_capture_group(pattern, group, strict=strict) + return pattern + + +############################# +# syntax-related utils + +_PAREN_RE = re.compile(rf''' + (?: + (?: + [^'"()]* + {_ind(STRING_LITERAL, 3)} + )* + [^'"()]* + (?: + ( [(] ) + | + ( [)] ) + ) + ) + ''', re.VERBOSE) + + +def match_paren(text, depth=0): + pos = 0 + while (m := _PAREN_RE.match(text, pos)): + pos = m.end() + _open, _close = m.groups() + if _open: + depth += 1 + else: # _close + depth -= 1 + if depth == 0: + return pos + else: + raise ValueError(f'could not find matching parens for {text!r}') + + +VAR_DECL = set_capture_groups(_VAR_DECL, ( + 'STORAGE', + 'TYPE_QUAL', + 'TYPE_SPEC', + 'DECLARATOR', + 'IDENTIFIER', + 'WRAPPED_IDENTIFIER', + 'FUNC_IDENTIFIER', +)) + + +def parse_var_decl(decl): + m = re.match(VAR_DECL, decl, re.VERBOSE) + (storage, typequal, typespec, declarator, + name, + wrappedname, + funcptrname, + ) = m.groups() + if name: + kind = 'simple' + elif wrappedname: + kind = 'wrapped' + name = wrappedname + elif funcptrname: + kind = 'funcptr' + name = funcptrname + else: + raise NotImplementedError + abstract = declarator.replace(name, '') + vartype = { + 'storage': storage, + 'typequal': typequal, + 'typespec': typespec, + 'abstract': abstract, + } + return (kind, name, vartype) + + +############################# +# parser state utils + +# XXX Drop this or use it! +def iter_results(results): + if not results: + return + if callable(results): + results = results() + + for result, text in results(): + if result: + yield result, text diff --git a/Tools/c-analyzer/c_parser/parser/_compound_decl_body.py b/Tools/c-analyzer/c_parser/parser/_compound_decl_body.py new file mode 100644 index 00000000000..eb5bc67607b --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_compound_decl_body.py @@ -0,0 +1,158 @@ +import re + +from ._regexes import ( + STRUCT_MEMBER_DECL as _STRUCT_MEMBER_DECL, + ENUM_MEMBER_DECL as _ENUM_MEMBER_DECL, +) +from ._common import ( + log_match, + parse_var_decl, + set_capture_groups, +) + + +############################# +# struct / union + +STRUCT_MEMBER_DECL = set_capture_groups(_STRUCT_MEMBER_DECL, ( + 'COMPOUND_TYPE_KIND', + 'COMPOUND_TYPE_NAME', + 'SPECIFIER_QUALIFIER', + 'DECLARATOR', + 'SIZE', + 'ENDING', + 'CLOSE', +)) +STRUCT_MEMBER_RE = re.compile(rf'^ \s* {STRUCT_MEMBER_DECL}', re.VERBOSE) + + +def parse_struct_body(source, anon_name, parent): + done = False + while not done: + done = True + for srcinfo in source: + m = STRUCT_MEMBER_RE.match(srcinfo.text) + if m: + break + else: + # We ran out of lines. + if srcinfo is not None: + srcinfo.done() + return + for item in _parse_struct_next(m, srcinfo, anon_name, parent): + if callable(item): + parse_body = item + yield from parse_body(source) + else: + yield item + done = False + + +def _parse_struct_next(m, srcinfo, anon_name, parent): + (inline_kind, inline_name, + qualspec, declarator, + size, + ending, + close, + ) = m.groups() + remainder = srcinfo.text[m.end():] + + if close: + log_match('compound close', m) + srcinfo.advance(remainder) + + elif inline_kind: + log_match('compound inline', m) + kind = inline_kind + name = inline_name or anon_name('inline-') + # Immediately emit a forward declaration. + yield srcinfo.resolve(kind, name=name, data=None) + + # un-inline the decl. Note that it might not actually be inline. + # We handle the case in the "maybe_inline_actual" branch. + srcinfo.nest( + remainder, + f'{kind} {name}', + ) + def parse_body(source): + _parse_body = DECL_BODY_PARSERS[kind] + + data = [] # members + ident = f'{kind} {name}' + for item in _parse_body(source, anon_name, ident): + if item.kind == 'field': + data.append(item) + else: + yield item + # XXX Should "parent" really be None for inline type decls? + yield srcinfo.resolve(kind, data, name, parent=None) + + srcinfo.resume() + yield parse_body + + else: + # not inline (member) + log_match('compound member', m) + if qualspec: + _, name, data = parse_var_decl(f'{qualspec} {declarator}') + if not name: + name = anon_name('struct-field-') + if size: +# data = (data, size) + data['size'] = int(size) + else: + # This shouldn't happen (we expect each field to have a name). + raise NotImplementedError + name = sized_name or anon_name('struct-field-') + data = int(size) + + yield srcinfo.resolve('field', data, name, parent) # XXX Restart? + if ending == ',': + remainder = rf'{qualspec} {remainder}' + srcinfo.advance(remainder) + + +############################# +# enum + +ENUM_MEMBER_DECL = set_capture_groups(_ENUM_MEMBER_DECL, ( + 'CLOSE', + 'NAME', + 'INIT', + 'ENDING', +)) +ENUM_MEMBER_RE = re.compile(rf'{ENUM_MEMBER_DECL}', re.VERBOSE) + + +def parse_enum_body(source, _anon_name, _parent): + ending = None + while ending != '}': + for srcinfo in source: + m = ENUM_MEMBER_RE.match(srcinfo.text) + if m: + break + else: + # We ran out of lines. + if srcinfo is not None: + srcinfo.done() + return + remainder = srcinfo.text[m.end():] + + (close, + name, init, ending, + ) = m.groups() + if close: + ending = '}' + else: + data = init + yield srcinfo.resolve('field', data, name, _parent) + srcinfo.advance(remainder) + + +############################# + +DECL_BODY_PARSERS = { + 'struct': parse_struct_body, + 'union': parse_struct_body, + 'enum': parse_enum_body, +} diff --git a/Tools/c-analyzer/c_parser/parser/_delim.py b/Tools/c-analyzer/c_parser/parser/_delim.py new file mode 100644 index 00000000000..51433a629d3 --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_delim.py @@ -0,0 +1,54 @@ +import re +import textwrap + +from ._regexes import _ind, STRING_LITERAL + + +def parse(text, anon_name): + context = None + data = None + for m in DELIMITER_RE.find_iter(text): + before, opened, closed = m.groups() + delim = opened or closed + + handle_segment = HANDLERS[context][delim] + result, context, data = handle_segment(before, delim, data) + if result: + yield result + + +DELIMITER = textwrap.dedent(rf''' + ( + (?: + [^'"()\[\]{};]* + {_ind(STRING_LITERAL, 3)} + }* + [^'"()\[\]{};]+ + )? # <before> + (?: + ( + [(\[{] + ) # <open> + | + ( + [)\]};] + ) # <close> + )? + ''') +DELIMITER_RE = re.compile(DELIMITER, re.VERBOSE) + +_HANDLERS = { + None: { # global + # opened + '{': ..., + '[': None, + '(': None, + # closed + '}': None, + ']': None, + ')': None, + ';': ..., + }, + '': { + }, +} diff --git a/Tools/c-analyzer/c_parser/parser/_func_body.py b/Tools/c-analyzer/c_parser/parser/_func_body.py new file mode 100644 index 00000000000..42fd459e111 --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_func_body.py @@ -0,0 +1,278 @@ +import re + +from ._regexes import ( + LOCAL as _LOCAL, + LOCAL_STATICS as _LOCAL_STATICS, +) +from ._common import ( + log_match, + parse_var_decl, + set_capture_groups, + match_paren, +) +from ._compound_decl_body import DECL_BODY_PARSERS + + +LOCAL = set_capture_groups(_LOCAL, ( + 'EMPTY', + 'INLINE_LEADING', + 'INLINE_PRE', + 'INLINE_KIND', + 'INLINE_NAME', + 'STORAGE', + 'VAR_DECL', + 'VAR_INIT', + 'VAR_ENDING', + 'COMPOUND_BARE', + 'COMPOUND_LABELED', + 'COMPOUND_PAREN', + 'BLOCK_LEADING', + 'BLOCK_OPEN', + 'SIMPLE_STMT', + 'SIMPLE_ENDING', + 'BLOCK_CLOSE', +)) +LOCAL_RE = re.compile(rf'^ \s* {LOCAL}', re.VERBOSE) + + +# Note that parse_function_body() still has trouble with a few files +# in the CPython codebase. + +def parse_function_body(source, name, anon_name): + # XXX + raise NotImplementedError + + +def parse_function_body(name, text, resolve, source, anon_name, parent): + raise NotImplementedError + # For now we do not worry about locals declared in for loop "headers". + depth = 1; + while depth > 0: + m = LOCAL_RE.match(text) + while not m: + text, resolve = continue_text(source, text or '{', resolve) + m = LOCAL_RE.match(text) + text = text[m.end():] + ( + empty, + inline_leading, inline_pre, inline_kind, inline_name, + storage, decl, + var_init, var_ending, + compound_bare, compound_labeled, compound_paren, + block_leading, block_open, + simple_stmt, simple_ending, + block_close, + ) = m.groups() + + if empty: + log_match('', m) + resolve(None, None, None, text) + yield None, text + elif inline_kind: + log_match('', m) + kind = inline_kind + name = inline_name or anon_name('inline-') + data = [] # members + # We must set the internal "text" from _iter_source() to the + # start of the inline compound body, + # Note that this is effectively like a forward reference that + # we do not emit. + resolve(kind, None, name, text, None) + _parse_body = DECL_BODY_PARSERS[kind] + before = [] + ident = f'{kind} {name}' + for member, inline, text in _parse_body(text, resolve, source, anon_name, ident): + if member: + data.append(member) + if inline: + yield from inline + # un-inline the decl. Note that it might not actually be inline. + # We handle the case in the "maybe_inline_actual" branch. + text = f'{inline_leading or ""} {inline_pre or ""} {kind} {name} {text}' + # XXX Should "parent" really be None for inline type decls? + yield resolve(kind, data, name, text, None), text + elif block_close: + log_match('', m) + depth -= 1 + resolve(None, None, None, text) + # XXX This isn't great. Calling resolve() should have + # cleared the closing bracket. However, some code relies + # on the yielded value instead of the resolved one. That + # needs to be fixed. + yield None, text + elif compound_bare: + log_match('', m) + yield resolve('statement', compound_bare, None, text, parent), text + elif compound_labeled: + log_match('', m) + yield resolve('statement', compound_labeled, None, text, parent), text + elif compound_paren: + log_match('', m) + try: + pos = match_paren(text) + except ValueError: + text = f'{compound_paren} {text}' + #resolve(None, None, None, text) + text, resolve = continue_text(source, text, resolve) + yield None, text + else: + head = text[:pos] + text = text[pos:] + if compound_paren == 'for': + # XXX Parse "head" as a compound statement. + stmt1, stmt2, stmt3 = head.split(';', 2) + data = { + 'compound': compound_paren, + 'statements': (stmt1, stmt2, stmt3), + } + else: + data = { + 'compound': compound_paren, + 'statement': head, + } + yield resolve('statement', data, None, text, parent), text + elif block_open: + log_match('', m) + depth += 1 + if block_leading: + # An inline block: the last evaluated expression is used + # in place of the block. + # XXX Combine it with the remainder after the block close. + stmt = f'{block_open}{{<expr>}}...;' + yield resolve('statement', stmt, None, text, parent), text + else: + resolve(None, None, None, text) + yield None, text + elif simple_ending: + log_match('', m) + yield resolve('statement', simple_stmt, None, text, parent), text + elif var_ending: + log_match('', m) + kind = 'variable' + _, name, vartype = parse_var_decl(decl) + data = { + 'storage': storage, + 'vartype': vartype, + } + after = () + if var_ending == ',': + # It was a multi-declaration, so queue up the next one. + _, qual, typespec, _ = vartype.values() + text = f'{storage or ""} {qual or ""} {typespec} {text}' + yield resolve(kind, data, name, text, parent), text + if var_init: + _data = f'{name} = {var_init.strip()}' + yield resolve('statement', _data, None, text, parent), text + else: + # This should be unreachable. + raise NotImplementedError + + +############################# +# static local variables + +LOCAL_STATICS = set_capture_groups(_LOCAL_STATICS, ( + 'INLINE_LEADING', + 'INLINE_PRE', + 'INLINE_KIND', + 'INLINE_NAME', + 'STATIC_DECL', + 'STATIC_INIT', + 'STATIC_ENDING', + 'DELIM_LEADING', + 'BLOCK_OPEN', + 'BLOCK_CLOSE', + 'STMT_END', +)) +LOCAL_STATICS_RE = re.compile(rf'^ \s* {LOCAL_STATICS}', re.VERBOSE) + + +def parse_function_statics(source, func, anon_name): + # For now we do not worry about locals declared in for loop "headers". + depth = 1; + while depth > 0: + for srcinfo in source: + m = LOCAL_STATICS_RE.match(srcinfo.text) + if m: + break + else: + # We ran out of lines. + if srcinfo is not None: + srcinfo.done() + return + for item, depth in _parse_next_local_static(m, srcinfo, + anon_name, func, depth): + if callable(item): + parse_body = item + yield from parse_body(source) + elif item is not None: + yield item + + +def _parse_next_local_static(m, srcinfo, anon_name, func, depth): + (inline_leading, inline_pre, inline_kind, inline_name, + static_decl, static_init, static_ending, + _delim_leading, + block_open, + block_close, + stmt_end, + ) = m.groups() + remainder = srcinfo.text[m.end():] + + if inline_kind: + log_match('func inline', m) + kind = inline_kind + name = inline_name or anon_name('inline-') + # Immediately emit a forward declaration. + yield srcinfo.resolve(kind, name=name, data=None), depth + + # un-inline the decl. Note that it might not actually be inline. + # We handle the case in the "maybe_inline_actual" branch. + srcinfo.nest( + remainder, + f'{inline_leading or ""} {inline_pre or ""} {kind} {name}' + ) + def parse_body(source): + _parse_body = DECL_BODY_PARSERS[kind] + + data = [] # members + ident = f'{kind} {name}' + for item in _parse_body(source, anon_name, ident): + if item.kind == 'field': + data.append(item) + else: + yield item + # XXX Should "parent" really be None for inline type decls? + yield srcinfo.resolve(kind, data, name, parent=None) + + srcinfo.resume() + yield parse_body, depth + + elif static_decl: + log_match('local variable', m) + _, name, data = parse_var_decl(static_decl) + + yield srcinfo.resolve('variable', data, name, parent=func), depth + + if static_init: + srcinfo.advance(f'{name} {static_init} {remainder}') + elif static_ending == ',': + # It was a multi-declaration, so queue up the next one. + _, qual, typespec, _ = data.values() + srcinfo.advance(f'static {qual or ""} {typespec} {remainder}') + else: + srcinfo.advance('') + + else: + log_match('func other', m) + if block_open: + depth += 1 + elif block_close: + depth -= 1 + elif stmt_end: + pass + else: + # This should be unreachable. + raise NotImplementedError + srcinfo.advance(remainder) + yield None, depth diff --git a/Tools/c-analyzer/c_parser/parser/_global.py b/Tools/c-analyzer/c_parser/parser/_global.py new file mode 100644 index 00000000000..35947c12998 --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_global.py @@ -0,0 +1,179 @@ +import re + +from ._regexes import ( + GLOBAL as _GLOBAL, +) +from ._common import ( + log_match, + parse_var_decl, + set_capture_groups, +) +from ._compound_decl_body import DECL_BODY_PARSERS +#from ._func_body import parse_function_body +from ._func_body import parse_function_statics as parse_function_body + + +GLOBAL = set_capture_groups(_GLOBAL, ( + 'EMPTY', + 'COMPOUND_LEADING', + 'COMPOUND_KIND', + 'COMPOUND_NAME', + 'FORWARD_KIND', + 'FORWARD_NAME', + 'MAYBE_INLINE_ACTUAL', + 'TYPEDEF_DECL', + 'TYPEDEF_FUNC_PARAMS', + 'VAR_STORAGE', + 'FUNC_INLINE', + 'VAR_DECL', + 'FUNC_PARAMS', + 'FUNC_DELIM', + 'FUNC_LEGACY_PARAMS', + 'VAR_INIT', + 'VAR_ENDING', +)) +GLOBAL_RE = re.compile(rf'^ \s* {GLOBAL}', re.VERBOSE) + + +def parse_globals(source, anon_name): + for srcinfo in source: + m = GLOBAL_RE.match(srcinfo.text) + if not m: + # We need more text. + continue + for item in _parse_next(m, srcinfo, anon_name): + if callable(item): + parse_body = item + yield from parse_body(source) + else: + yield item + else: + # We ran out of lines. + if srcinfo is not None: + srcinfo.done() + return + + +def _parse_next(m, srcinfo, anon_name): + ( + empty, + # compound type decl (maybe inline) + compound_leading, compound_kind, compound_name, + forward_kind, forward_name, maybe_inline_actual, + # typedef + typedef_decl, typedef_func_params, + # vars and funcs + storage, func_inline, decl, + func_params, func_delim, func_legacy_params, + var_init, var_ending, + ) = m.groups() + remainder = srcinfo.text[m.end():] + + if empty: + log_match('global empty', m) + srcinfo.advance(remainder) + + elif maybe_inline_actual: + log_match('maybe_inline_actual', m) + # Ignore forward declarations. + # XXX Maybe return them too (with an "isforward" flag)? + if not maybe_inline_actual.strip().endswith(';'): + remainder = maybe_inline_actual + remainder + yield srcinfo.resolve(forward_kind, None, forward_name) + if maybe_inline_actual.strip().endswith('='): + # We use a dummy prefix for a fake typedef. + # XXX Ideally this case would not be caught by MAYBE_INLINE_ACTUAL. + _, name, data = parse_var_decl(f'{forward_kind} {forward_name} fake_typedef_{forward_name}') + yield srcinfo.resolve('typedef', data, name, parent=None) + remainder = f'{name} {remainder}' + srcinfo.advance(remainder) + + elif compound_kind: + kind = compound_kind + name = compound_name or anon_name('inline-') + # Immediately emit a forward declaration. + yield srcinfo.resolve(kind, name=name, data=None) + + # un-inline the decl. Note that it might not actually be inline. + # We handle the case in the "maybe_inline_actual" branch. + srcinfo.nest( + remainder, + f'{compound_leading or ""} {compound_kind} {name}', + ) + def parse_body(source): + _parse_body = DECL_BODY_PARSERS[compound_kind] + + data = [] # members + ident = f'{kind} {name}' + for item in _parse_body(source, anon_name, ident): + if item.kind == 'field': + data.append(item) + else: + yield item + # XXX Should "parent" really be None for inline type decls? + yield srcinfo.resolve(kind, data, name, parent=None) + + srcinfo.resume() + yield parse_body + + elif typedef_decl: + log_match('typedef', m) + kind = 'typedef' + _, name, data = parse_var_decl(typedef_decl) + if typedef_func_params: + return_type = data + # This matches the data for func declarations. + data = { + 'storage': None, + 'inline': None, + 'params': f'({typedef_func_params})', + 'returntype': return_type, + 'isforward': True, + } + yield srcinfo.resolve(kind, data, name, parent=None) + srcinfo.advance(remainder) + + elif func_delim or func_legacy_params: + log_match('function', m) + kind = 'function' + _, name, return_type = parse_var_decl(decl) + func_params = func_params or func_legacy_params + data = { + 'storage': storage, + 'inline': func_inline, + 'params': f'({func_params})', + 'returntype': return_type, + 'isforward': func_delim == ';', + } + + yield srcinfo.resolve(kind, data, name, parent=None) + srcinfo.advance(remainder) + + if func_delim == '{' or func_legacy_params: + def parse_body(source): + yield from parse_function_body(source, name, anon_name) + yield parse_body + + elif var_ending: + log_match('global variable', m) + kind = 'variable' + _, name, vartype = parse_var_decl(decl) + data = { + 'storage': storage, + 'vartype': vartype, + } + yield srcinfo.resolve(kind, data, name, parent=None) + + if var_ending == ',': + # It was a multi-declaration, so queue up the next one. + _, qual, typespec, _ = vartype.values() + remainder = f'{storage or ""} {qual or ""} {typespec} {remainder}' + srcinfo.advance(remainder) + + if var_init: + _data = f'{name} = {var_init.strip()}' + yield srcinfo.resolve('statement', _data, name=None) + + else: + # This should be unreachable. + raise NotImplementedError diff --git a/Tools/c-analyzer/c_parser/parser/_info.py b/Tools/c-analyzer/c_parser/parser/_info.py new file mode 100644 index 00000000000..2dcd5e5e760 --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_info.py @@ -0,0 +1,168 @@ +from ..info import KIND, ParsedItem, FileInfo + + +class TextInfo: + + def __init__(self, text, start=None, end=None): + # immutable: + if not start: + start = 1 + self.start = start + + # mutable: + lines = text.splitlines() or [''] + self.text = text.strip() + if not end: + end = start + len(lines) - 1 + self.end = end + self.line = lines[-1] + + def __repr__(self): + args = (f'{a}={getattr(self, a)!r}' + for a in ['text', 'start', 'end']) + return f'{type(self).__name__}({", ".join(args)})' + + def add_line(self, line, lno=None): + if lno is None: + lno = self.end + 1 + else: + if isinstance(lno, FileInfo): + fileinfo = lno + if fileinfo.filename != self.filename: + raise NotImplementedError((fileinfo, self.filename)) + lno = fileinfo.lno + # XXX + #if lno < self.end: + # raise NotImplementedError((lno, self.end)) + line = line.lstrip() + self.text += ' ' + line + self.line = line + self.end = lno + + +class SourceInfo: + + _ready = False + + def __init__(self, filename, _current=None): + # immutable: + self.filename = filename + # mutable: + if isinstance(_current, str): + _current = TextInfo(_current) + self._current = _current + start = -1 + self._start = _current.start if _current else -1 + self._nested = [] + self._set_ready() + + def __repr__(self): + args = (f'{a}={getattr(self, a)!r}' + for a in ['filename', '_current']) + return f'{type(self).__name__}({", ".join(args)})' + + @property + def start(self): + if self._current is None: + return self._start + return self._current.start + + @property + def end(self): + if self._current is None: + return self._start + return self._current.end + + @property + def text(self): + if self._current is None: + return '' + return self._current.text + + def nest(self, text, before, start=None): + if self._current is None: + raise Exception('nesting requires active source text') + current = self._current + current.text = before + self._nested.append(current) + self._replace(text, start) + + def resume(self, remainder=None): + if not self._nested: + raise Exception('no nested text to resume') + if self._current is None: + raise Exception('un-nesting requires active source text') + if remainder is None: + remainder = self._current.text + self._clear() + self._current = self._nested.pop() + self._current.text += ' ' + remainder + self._set_ready() + + def advance(self, remainder, start=None): + if self._current is None: + raise Exception('advancing requires active source text') + if remainder.strip(): + self._replace(remainder, start, fixnested=True) + else: + if self._nested: + self._replace('', start, fixnested=True) + #raise Exception('cannot advance while nesting') + else: + self._clear(start) + + def resolve(self, kind, data, name, parent=None): + # "field" isn't a top-level kind, so we leave it as-is. + if kind and kind != 'field': + kind = KIND._from_raw(kind) + fileinfo = FileInfo(self.filename, self._start) + return ParsedItem(fileinfo, kind, parent, name, data) + + def done(self): + self._set_ready() + + def _set_ready(self): + if self._current is None: + self._ready = False + else: + self._ready = self._current.text.strip() != '' + + def _used(self): + ready = self._ready + self._ready = False + return ready + + def _clear(self, start=None): + old = self._current + if self._current is not None: + # XXX Fail if self._current wasn't used up? + if start is None: + start = self._current.end + self._current = None + if start is not None: + self._start = start + self._set_ready() + return old + + def _replace(self, text, start=None, *, fixnested=False): + end = self._current.end + old = self._clear(start) + self._current = TextInfo(text, self._start, end) + if fixnested and self._nested and self._nested[-1] is old: + self._nested[-1] = self._current + self._set_ready() + + def _add_line(self, line, lno=None): + if not line.strip(): + # We don't worry about multi-line string literals. + return + if self._current is None: + self._start = lno + self._current = TextInfo(line, lno) + else: + # XXX + #if lno < self._current.end: + # # A circular include? + # raise NotImplementedError((lno, self)) + self._current.add_line(line, lno) + self._ready = True diff --git a/Tools/c-analyzer/c_parser/parser/_regexes.py b/Tools/c-analyzer/c_parser/parser/_regexes.py new file mode 100644 index 00000000000..e9bc31d335a --- /dev/null +++ b/Tools/c-analyzer/c_parser/parser/_regexes.py @@ -0,0 +1,796 @@ +# Regular expression patterns for C syntax. +# +# None of these patterns has any capturing. However, a number of them +# have capturing markers compatible with utils.set_capture_groups(). + +import textwrap + + +def _ind(text, level=1, edges='both'): + indent = ' ' * level + text = textwrap.indent(text, indent) + if edges == 'pre' or edges == 'both': + text = '\n' + indent + text.lstrip() + if edges == 'post' or edges == 'both': + text = text.rstrip() + '\n' + ' ' * (level - 1) + return text + + +####################################### +# general + +HEX = r'(?: [0-9a-zA-Z] )' + +STRING_LITERAL = textwrap.dedent(rf''' + (?: + # character literal + (?: + ['] [^'] ['] + | + ['] \\ . ['] + | + ['] \\x{HEX}{HEX} ['] + | + ['] \\0\d\d ['] + | + (?: + ['] \\o[01]\d\d ['] + | + ['] \\o2[0-4]\d ['] + | + ['] \\o25[0-5] ['] + ) + ) + | + # string literal + (?: + ["] (?: [^"\\]* \\ . )* [^"\\]* ["] + ) + # end string literal + ) + ''') + +_KEYWORD = textwrap.dedent(r''' + (?: + \b + (?: + auto | + extern | + register | + static | + typedef | + + const | + volatile | + + signed | + unsigned | + char | + short | + int | + long | + float | + double | + void | + + struct | + union | + enum | + + goto | + return | + sizeof | + break | + continue | + if | + else | + for | + do | + while | + switch | + case | + default | + entry + ) + \b + ) + ''') +KEYWORD = rf''' + # keyword + {_KEYWORD} + # end keyword + ''' +_KEYWORD = ''.join(_KEYWORD.split()) + +IDENTIFIER = r'(?: [a-zA-Z_][a-zA-Z0-9_]* )' +# We use a negative lookahead to filter out keywords. +STRICT_IDENTIFIER = rf'(?: (?! {_KEYWORD} ) \b {IDENTIFIER} \b )' +ANON_IDENTIFIER = rf'(?: (?! {_KEYWORD} ) \b {IDENTIFIER} (?: - \d+ )? \b )' + + +####################################### +# types + +SIMPLE_TYPE = textwrap.dedent(rf''' + # simple type + (?: + \b + (?: + void + | + (?: signed | unsigned ) # implies int + | + (?: + (?: (?: signed | unsigned ) \s+ )? + (?: (?: long | short ) \s+ )? + (?: char | short | int | long | float | double ) + ) + ) + \b + ) + # end simple type + ''') + +COMPOUND_TYPE_KIND = r'(?: \b (?: struct | union | enum ) \b )' + + +####################################### +# variable declarations + +STORAGE_CLASS = r'(?: \b (?: auto | register | static | extern ) \b )' +TYPE_QUALIFIER = r'(?: \b (?: const | volatile ) \b )' +PTR_QUALIFIER = rf'(?: [*] (?: \s* {TYPE_QUALIFIER} )? )' + +TYPE_SPEC = textwrap.dedent(rf''' + # type spec + (?: + {_ind(SIMPLE_TYPE, 2)} + | + (?: + [_]*typeof[_]* + \s* [(] + (?: \s* [*&] )* + \s* {STRICT_IDENTIFIER} + \s* [)] + ) + | + # reference to a compound type + (?: + {COMPOUND_TYPE_KIND} + (?: \s* {ANON_IDENTIFIER} )? + ) + | + # reference to a typedef + {STRICT_IDENTIFIER} + ) + # end type spec + ''') + +DECLARATOR = textwrap.dedent(rf''' + # declarator (possibly abstract) + (?: + (?: {PTR_QUALIFIER} \s* )* + (?: + (?: + (?: # <IDENTIFIER> + {STRICT_IDENTIFIER} + ) + (?: \s* \[ (?: \s* [^\]]+ \s* )? [\]] )* # arrays + ) + | + (?: + [(] \s* + (?: # <WRAPPED_IDENTIFIER> + {STRICT_IDENTIFIER} + ) + (?: \s* \[ (?: \s* [^\]]+ \s* )? [\]] )* # arrays + \s* [)] + ) + | + # func ptr + (?: + [(] (?: \s* {PTR_QUALIFIER} )? \s* + (?: # <FUNC_IDENTIFIER> + {STRICT_IDENTIFIER} + ) + (?: \s* \[ (?: \s* [^\]]+ \s* )? [\]] )* # arrays + \s* [)] + # We allow for a single level of paren nesting in parameters. + \s* [(] (?: [^()]* [(] [^)]* [)] )* [^)]* [)] + ) + ) + ) + # end declarator + ''') + +VAR_DECL = textwrap.dedent(rf''' + # var decl (and typedef and func return type) + (?: + (?: + (?: # <STORAGE> + {STORAGE_CLASS} + ) + \s* + )? + (?: + (?: # <TYPE_QUAL> + {TYPE_QUALIFIER} + ) + \s* + )? + (?: + (?: # <TYPE_SPEC> + {_ind(TYPE_SPEC, 4)} + ) + ) + \s* + (?: + (?: # <DECLARATOR> + {_ind(DECLARATOR, 4)} + ) + ) + ) + # end var decl + ''') + +INITIALIZER = textwrap.dedent(rf''' + # initializer + (?: + (?: + [(] + # no nested parens (e.g. func ptr) + [^)]* + [)] + \s* + )? + (?: + # a string literal + (?: + (?: {_ind(STRING_LITERAL, 4)} \s* )* + {_ind(STRING_LITERAL, 4)} + ) + | + + # a simple initializer + (?: + (?: + [^'",;{{]* + {_ind(STRING_LITERAL, 4)} + )* + [^'",;{{]* + ) + | + + # a struct/array literal + (?: + # We only expect compound initializers with + # single-variable declarations. + {{ + (?: + [^'";]*? + {_ind(STRING_LITERAL, 5)} + )* + [^'";]*? + }} + (?= \s* ; ) # Note this lookahead. + ) + ) + ) + # end initializer + ''') + + +####################################### +# compound type declarations + +STRUCT_MEMBER_DECL = textwrap.dedent(rf''' + (?: + # inline compound type decl + (?: + (?: # <COMPOUND_TYPE_KIND> + {COMPOUND_TYPE_KIND} + ) + (?: + \s+ + (?: # <COMPOUND_TYPE_NAME> + {STRICT_IDENTIFIER} + ) + )? + \s* {{ + ) + | + (?: + # typed member + (?: + # Technically it doesn't have to have a type... + (?: # <SPECIFIER_QUALIFIER> + (?: {TYPE_QUALIFIER} \s* )? + {_ind(TYPE_SPEC, 5)} + ) + (?: + # If it doesn't have a declarator then it will have + # a size and vice versa. + \s* + (?: # <DECLARATOR> + {_ind(DECLARATOR, 6)} + ) + )? + ) + + # sized member + (?: + \s* [:] \s* + (?: # <SIZE> + \d+ + ) + )? + \s* + (?: # <ENDING> + [,;] + ) + ) + | + (?: + \s* + (?: # <CLOSE> + }} + ) + ) + ) + ''') + +ENUM_MEMBER_DECL = textwrap.dedent(rf''' + (?: + (?: + \s* + (?: # <CLOSE> + }} + ) + ) + | + (?: + \s* + (?: # <NAME> + {IDENTIFIER} + ) + (?: + \s* = \s* + (?: # <INIT> + {_ind(STRING_LITERAL, 4)} + | + [^'",}}]+ + ) + )? + \s* + (?: # <ENDING> + , | }} + ) + ) + ) + ''') + + +####################################### +# statements + +SIMPLE_STMT_BODY = textwrap.dedent(rf''' + # simple statement body + (?: + (?: + [^'"{{}};]* + {_ind(STRING_LITERAL, 3)} + )* + [^'"{{}};]* + #(?= [;{{] ) # Note this lookahead. + ) + # end simple statement body + ''') +SIMPLE_STMT = textwrap.dedent(rf''' + # simple statement + (?: + (?: # <SIMPLE_STMT> + # stmt-inline "initializer" + (?: + return \b + (?: + \s* + {_ind(INITIALIZER, 5)} + )? + ) + | + # variable assignment + (?: + (?: [*] \s* )? + (?: + {STRICT_IDENTIFIER} \s* + (?: . | -> ) \s* + )* + {STRICT_IDENTIFIER} + (?: \s* \[ \s* \d+ \s* \] )? + \s* = \s* + {_ind(INITIALIZER, 4)} + ) + | + # catchall return statement + (?: + return \b + (?: + (?: + [^'";]* + {_ind(STRING_LITERAL, 6)} + )* + \s* [^'";]* + )? + ) + | + # simple statement + (?: + {_ind(SIMPLE_STMT_BODY, 4)} + ) + ) + \s* + (?: # <SIMPLE_ENDING> + ; + ) + ) + # end simple statement + ''') +COMPOUND_STMT = textwrap.dedent(rf''' + # compound statement + (?: + \b + (?: + (?: + (?: # <COMPOUND_BARE> + else | do + ) + \b + ) + | + (?: + (?: # <COMPOUND_LABELED> + (?: + case \b + (?: + [^'":]* + {_ind(STRING_LITERAL, 7)} + )* + \s* [^'":]* + ) + | + default + | + {STRICT_IDENTIFIER} + ) + \s* [:] + ) + | + (?: + (?: # <COMPOUND_PAREN> + for | while | if | switch + ) + \s* (?= [(] ) # Note this lookahead. + ) + ) + \s* + ) + # end compound statement + ''') + + +####################################### +# function bodies + +LOCAL = textwrap.dedent(rf''' + (?: + # an empty statement + (?: # <EMPTY> + ; + ) + | + # inline type decl + (?: + (?: + (?: # <INLINE_LEADING> + [^;{{}}]+? + ) + \s* + )? + (?: # <INLINE_PRE> + (?: {STORAGE_CLASS} \s* )? + (?: {TYPE_QUALIFIER} \s* )? + )? # </INLINE_PRE> + (?: # <INLINE_KIND> + {COMPOUND_TYPE_KIND} + ) + (?: + \s+ + (?: # <INLINE_NAME> + {STRICT_IDENTIFIER} + ) + )? + \s* {{ + ) + | + # var decl + (?: + (?: # <STORAGE> + {STORAGE_CLASS} + )? # </STORAGE> + (?: + \s* + (?: # <VAR_DECL> + {_ind(VAR_DECL, 5)} + ) + ) + (?: + (?: + # initializer + # We expect only basic initializers. + \s* = \s* + (?: # <VAR_INIT> + {_ind(INITIALIZER, 6)} + ) + )? + (?: + \s* + (?: # <VAR_ENDING> + [,;] + ) + ) + ) + ) + | + {_ind(COMPOUND_STMT, 2)} + | + # start-of-block + (?: + (?: # <BLOCK_LEADING> + (?: + [^'"{{}};]* + {_ind(STRING_LITERAL, 5)} + )* + [^'"{{}};]* + # Presumably we will not see "== {{". + [^\s='"{{}});] + \s* + )? # </BLOCK_LEADING> + (?: # <BLOCK_OPEN> + {{ + ) + ) + | + {_ind(SIMPLE_STMT, 2)} + | + # end-of-block + (?: # <BLOCK_CLOSE> + }} + ) + ) + ''') + +LOCAL_STATICS = textwrap.dedent(rf''' + (?: + # inline type decl + (?: + (?: + (?: # <INLINE_LEADING> + [^;{{}}]+? + ) + \s* + )? + (?: # <INLINE_PRE> + (?: {STORAGE_CLASS} \s* )? + (?: {TYPE_QUALIFIER} \s* )? + )? + (?: # <INLINE_KIND> + {COMPOUND_TYPE_KIND} + ) + (?: + \s+ + (?: # <INLINE_NAME> + {STRICT_IDENTIFIER} + ) + )? + \s* {{ + ) + | + # var decl + (?: + # We only look for static variables. + (?: # <STATIC_DECL> + static \b + (?: \s* {TYPE_QUALIFIER} )? + \s* {_ind(TYPE_SPEC, 4)} + \s* {_ind(DECLARATOR, 4)} + ) + \s* + (?: + (?: # <STATIC_INIT> + = \s* + {_ind(INITIALIZER, 4)} + \s* + [,;{{] + ) + | + (?: # <STATIC_ENDING> + [,;] + ) + ) + ) + | + # everything else + (?: + (?: # <DELIM_LEADING> + (?: + [^'"{{}};]* + {_ind(STRING_LITERAL, 4)} + )* + \s* [^'"{{}};]* + ) + (?: + (?: # <BLOCK_OPEN> + {{ + ) + | + (?: # <BLOCK_CLOSE> + }} + ) + | + (?: # <STMT_END> + ; + ) + ) + ) + ) + ''') + + +####################################### +# global declarations + +GLOBAL = textwrap.dedent(rf''' + (?: + # an empty statement + (?: # <EMPTY> + ; + ) + | + + # compound type decl (maybe inline) + (?: + (?: + (?: # <COMPOUND_LEADING> + [^;{{}}]+? + ) + \s* + )? + (?: # <COMPOUND_KIND> + {COMPOUND_TYPE_KIND} + ) + (?: + \s+ + (?: # <COMPOUND_NAME> + {STRICT_IDENTIFIER} + ) + )? + \s* {{ + ) + | + # bogus inline decl artifact + # This simplifies resolving the relative syntactic ambiguity of + # inline structs. + (?: + (?: # <FORWARD_KIND> + {COMPOUND_TYPE_KIND} + ) + \s* + (?: # <FORWARD_NAME> + {ANON_IDENTIFIER} + ) + (?: # <MAYBE_INLINE_ACTUAL> + [^=,;({{[*\]]* + [=,;({{] + ) + ) + | + + # typedef + (?: + \b typedef \b \s* + (?: # <TYPEDEF_DECL> + {_ind(VAR_DECL, 4)} + ) + (?: + # We expect no inline type definitions in the parameters. + \s* [(] \s* + (?: # <TYPEDEF_FUNC_PARAMS> + [^{{;]* + ) + \s* [)] + )? + \s* ; + ) + | + + # func decl/definition & var decls + # XXX dedicated pattern for funcs (more restricted)? + (?: + (?: + (?: # <VAR_STORAGE> + {STORAGE_CLASS} + ) + \s* + )? + (?: + (?: # <FUNC_INLINE> + \b inline \b + ) + \s* + )? + (?: # <VAR_DECL> + {_ind(VAR_DECL, 4)} + ) + (?: + # func decl / definition + (?: + (?: + # We expect no inline type definitions in the parameters. + \s* [(] \s* + (?: # <FUNC_PARAMS> + [^{{;]* + ) + \s* [)] \s* + (?: # <FUNC_DELIM> + [{{;] + ) + ) + | + (?: + # This is some old-school syntax! + \s* [(] \s* + # We throw away the bare names: + {STRICT_IDENTIFIER} + (?: \s* , \s* {STRICT_IDENTIFIER} )* + \s* [)] \s* + + # We keep the trailing param declarations: + (?: # <FUNC_LEGACY_PARAMS> + # There's at least one! + (?: {TYPE_QUALIFIER} \s* )? + {_ind(TYPE_SPEC, 7)} + \s* + {_ind(DECLARATOR, 7)} + \s* ; + (?: + \s* + (?: {TYPE_QUALIFIER} \s* )? + {_ind(TYPE_SPEC, 8)} + \s* + {_ind(DECLARATOR, 8)} + \s* ; + )* + ) + \s* {{ + ) + ) + | + # var / typedef + (?: + (?: + # initializer + # We expect only basic initializers. + \s* = \s* + (?: # <VAR_INIT> + {_ind(INITIALIZER, 6)} + ) + )? + \s* + (?: # <VAR_ENDING> + [,;] + ) + ) + ) + ) + ) + ''') diff --git a/Tools/c-analyzer/c_parser/preprocessor/__init__.py b/Tools/c-analyzer/c_parser/preprocessor/__init__.py new file mode 100644 index 00000000000..f206f694db5 --- /dev/null +++ b/Tools/c-analyzer/c_parser/preprocessor/__init__.py @@ -0,0 +1,190 @@ +import contextlib +import distutils.ccompiler +import logging +import os.path + +from c_common.fsutil import match_glob as _match_glob +from c_common.tables import parse_table as _parse_table +from ..source import ( + resolve as _resolve_source, + good_file as _good_file, +) +from . import errors as _errors +from . import ( + pure as _pure, + gcc as _gcc, +) + + +logger = logging.getLogger(__name__) + + +# Supprted "source": +# * filename (string) +# * lines (iterable) +# * text (string) +# Supported return values: +# * iterator of SourceLine +# * sequence of SourceLine +# * text (string) +# * something that combines all those +# XXX Add the missing support from above. +# XXX Add more low-level functions to handle permutations? + +def preprocess(source, *, + incldirs=None, + macros=None, + samefiles=None, + filename=None, + tool=True, + ): + """... + + CWD should be the project root and "source" should be relative. + """ + if tool: + logger.debug(f'CWD: {os.getcwd()!r}') + logger.debug(f'incldirs: {incldirs!r}') + logger.debug(f'macros: {macros!r}') + logger.debug(f'samefiles: {samefiles!r}') + _preprocess = _get_preprocessor(tool) + with _good_file(source, filename) as source: + return _preprocess(source, incldirs, macros, samefiles) or () + else: + source, filename = _resolve_source(source, filename) + # We ignore "includes", "macros", etc. + return _pure.preprocess(source, filename) + + # if _run() returns just the lines: +# text = _run(source) +# lines = [line + os.linesep for line in text.splitlines()] +# lines[-1] = lines[-1].splitlines()[0] +# +# conditions = None +# for lno, line in enumerate(lines, 1): +# kind = 'source' +# directive = None +# data = line +# yield lno, kind, data, conditions + + +def get_preprocessor(*, + file_macros=None, + file_incldirs=None, + file_same=None, + ignore_exc=False, + log_err=None, + ): + _preprocess = preprocess + if file_macros: + file_macros = tuple(_parse_macros(file_macros)) + if file_incldirs: + file_incldirs = tuple(_parse_incldirs(file_incldirs)) + if file_same: + file_same = tuple(file_same) + if not callable(ignore_exc): + ignore_exc = (lambda exc, _ig=ignore_exc: _ig) + + def get_file_preprocessor(filename): + filename = filename.strip() + if file_macros: + macros = list(_resolve_file_values(filename, file_macros)) + if file_incldirs: + incldirs = [v for v, in _resolve_file_values(filename, file_incldirs)] + + def preprocess(**kwargs): + if file_macros and 'macros' not in kwargs: + kwargs['macros'] = macros + if file_incldirs and 'incldirs' not in kwargs: + kwargs['incldirs'] = [v for v, in _resolve_file_values(filename, file_incldirs)] + if file_same and 'file_same' not in kwargs: + kwargs['samefiles'] = file_same + kwargs.setdefault('filename', filename) + with handling_errors(ignore_exc, log_err=log_err): + return _preprocess(filename, **kwargs) + return preprocess + return get_file_preprocessor + + +def _resolve_file_values(filename, file_values): + # We expect the filename and all patterns to be absolute paths. + for pattern, *value in file_values or (): + if _match_glob(filename, pattern): + yield value + + +def _parse_macros(macros): + for row, srcfile in _parse_table(macros, '\t', 'glob\tname\tvalue', rawsep='=', default=None): + yield row + + +def _parse_incldirs(incldirs): + for row, srcfile in _parse_table(incldirs, '\t', 'glob\tdirname', default=None): + glob, dirname = row + if dirname is None: + # Match all files. + dirname = glob + row = ('*', dirname.strip()) + yield row + + +@contextlib.contextmanager +def handling_errors(ignore_exc=None, *, log_err=None): + try: + yield + except _errors.OSMismatchError as exc: + if not ignore_exc(exc): + raise # re-raise + if log_err is not None: + log_err(f'<OS mismatch (expected {" or ".join(exc.expected)})>') + return None + except _errors.MissingDependenciesError as exc: + if not ignore_exc(exc): + raise # re-raise + if log_err is not None: + log_err(f'<missing dependency {exc.missing}') + return None + except _errors.ErrorDirectiveError as exc: + if not ignore_exc(exc): + raise # re-raise + if log_err is not None: + log_err(exc) + return None + + +################################## +# tools + +_COMPILERS = { + # matching disutils.ccompiler.compiler_class: + 'unix': _gcc.preprocess, + 'msvc': None, + 'cygwin': None, + 'mingw32': None, + 'bcpp': None, + # aliases/extras: + 'gcc': _gcc.preprocess, + 'clang': None, +} + + +def _get_preprocessor(tool): + if tool is True: + tool = distutils.ccompiler.get_default_compiler() + preprocess = _COMPILERS.get(tool) + if preprocess is None: + raise ValueError(f'unsupported tool {tool}') + return preprocess + + +################################## +# aliases + +from .errors import ( + PreprocessorError, + PreprocessorFailure, + ErrorDirectiveError, + MissingDependenciesError, + OSMismatchError, +) +from .common import FileInfo, SourceLine diff --git a/Tools/c-analyzer/c_parser/preprocessor/__main__.py b/Tools/c-analyzer/c_parser/preprocessor/__main__.py new file mode 100644 index 00000000000..a6054307c25 --- /dev/null +++ b/Tools/c-analyzer/c_parser/preprocessor/__main__.py @@ -0,0 +1,196 @@ +import logging +import sys + +from c_common.scriptutil import ( + CLIArgSpec as Arg, + add_verbosity_cli, + add_traceback_cli, + add_kind_filtering_cli, + add_files_cli, + add_failure_filtering_cli, + add_commands_cli, + process_args_by_key, + configure_logger, + get_prog, + main_for_filenames, +) +from . import ( + errors as _errors, + get_preprocessor as _get_preprocessor, +) + + +FAIL = { + 'err': _errors.ErrorDirectiveError, + 'deps': _errors.MissingDependenciesError, + 'os': _errors.OSMismatchError, +} +FAIL_DEFAULT = tuple(v for v in FAIL if v != 'os') + + +logger = logging.getLogger(__name__) + + +################################## +# CLI helpers + +def add_common_cli(parser, *, get_preprocessor=_get_preprocessor): + parser.add_argument('--macros', action='append') + parser.add_argument('--incldirs', action='append') + parser.add_argument('--same', action='append') + process_fail_arg = add_failure_filtering_cli(parser, FAIL) + + def process_args(args): + ns = vars(args) + + process_fail_arg(args) + ignore_exc = ns.pop('ignore_exc') + # We later pass ignore_exc to _get_preprocessor(). + + args.get_file_preprocessor = get_preprocessor( + file_macros=ns.pop('macros'), + file_incldirs=ns.pop('incldirs'), + file_same=ns.pop('same'), + ignore_exc=ignore_exc, + log_err=print, + ) + return process_args + + +def _iter_preprocessed(filename, *, + get_preprocessor, + match_kind=None, + pure=False, + ): + preprocess = get_preprocessor(filename) + for line in preprocess(tool=not pure) or (): + if match_kind is not None and not match_kind(line.kind): + continue + yield line + + +####################################### +# the commands + +def _cli_preprocess(parser, excluded=None, **prepr_kwargs): + parser.add_argument('--pure', action='store_true') + parser.add_argument('--no-pure', dest='pure', action='store_const', const=False) + process_kinds = add_kind_filtering_cli(parser) + process_common = add_common_cli(parser, **prepr_kwargs) + parser.add_argument('--raw', action='store_true') + process_files = add_files_cli(parser, excluded=excluded) + + return [ + process_kinds, + process_common, + process_files, + ] + + +def cmd_preprocess(filenames, *, + raw=False, + iter_filenames=None, + **kwargs + ): + if 'get_file_preprocessor' not in kwargs: + kwargs['get_file_preprocessor'] = _get_preprocessor() + if raw: + def show_file(filename, lines): + for line in lines: + print(line) + #print(line.raw) + else: + def show_file(filename, lines): + for line in lines: + linefile = '' + if line.filename != filename: + linefile = f' ({line.filename})' + text = line.data + if line.kind == 'comment': + text = '/* ' + line.data.splitlines()[0] + text += ' */' if '\n' in line.data else r'\n... */' + print(f' {line.lno:>4} {line.kind:10} | {text}') + + filenames = main_for_filenames(filenames, iter_filenames) + for filename in filenames: + lines = _iter_preprocessed(filename, **kwargs) + show_file(filename, lines) + + +def _cli_data(parser): + ... + + return None + + +def cmd_data(filenames, + **kwargs + ): + # XXX + raise NotImplementedError + + +COMMANDS = { + 'preprocess': ( + 'preprocess the given C source & header files', + [_cli_preprocess], + cmd_preprocess, + ), + 'data': ( + 'check/manage local data (e.g. excludes, macros)', + [_cli_data], + cmd_data, + ), +} + + +####################################### +# the script + +def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *, + subset='preprocess', + excluded=None, + **prepr_kwargs + ): + import argparse + parser = argparse.ArgumentParser( + prog=prog or get_prog(), + ) + + processors = add_commands_cli( + parser, + commands={k: v[1] for k, v in COMMANDS.items()}, + commonspecs=[ + add_verbosity_cli, + add_traceback_cli, + ], + subset=subset, + ) + + args = parser.parse_args(argv) + ns = vars(args) + + cmd = ns.pop('cmd') + + verbosity, traceback_cm = process_args_by_key( + args, + processors[cmd], + ['verbosity', 'traceback_cm'], + ) + + return cmd, ns, verbosity, traceback_cm + + +def main(cmd, cmd_kwargs): + try: + run_cmd = COMMANDS[cmd][0] + except KeyError: + raise ValueError(f'unsupported cmd {cmd!r}') + run_cmd(**cmd_kwargs) + + +if __name__ == '__main__': + cmd, cmd_kwargs, verbosity, traceback_cm = parse_args() + configure_logger(verbosity) + with traceback_cm: + main(cmd, cmd_kwargs) diff --git a/Tools/c-analyzer/c_parser/preprocessor/common.py b/Tools/c-analyzer/c_parser/preprocessor/common.py new file mode 100644 index 00000000000..63681025c63 --- /dev/null +++ b/Tools/c-analyzer/c_parser/preprocessor/common.py @@ -0,0 +1,173 @@ +import contextlib +import distutils.ccompiler +import logging +import shlex +import subprocess +import sys + +from ..info import FileInfo, SourceLine +from .errors import ( + PreprocessorFailure, + ErrorDirectiveError, + MissingDependenciesError, + OSMismatchError, +) + + +logger = logging.getLogger(__name__) + + +# XXX Add aggregate "source" class(es)? +# * expose all lines as single text string +# * expose all lines as sequence +# * iterate all lines + + +def run_cmd(argv, *, + #capture_output=True, + stdout=subprocess.PIPE, + #stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, + text=True, + check=True, + **kwargs + ): + if isinstance(stderr, str) and stderr.lower() == 'stdout': + stderr = subprocess.STDOUT + + kw = dict(locals()) + kw.pop('argv') + kw.pop('kwargs') + kwargs.update(kw) + + proc = subprocess.run(argv, **kwargs) + return proc.stdout + + +def preprocess(tool, filename, **kwargs): + argv = _build_argv(tool, filename, **kwargs) + logger.debug(' '.join(shlex.quote(v) for v in argv)) + + # Make sure the OS is supported for this file. + if (_expected := is_os_mismatch(filename)): + error = None + raise OSMismatchError(filename, _expected, argv, error, TOOL) + + # Run the command. + with converted_error(tool, argv, filename): + # We use subprocess directly here, instead of calling the + # distutil compiler object's preprocess() method, since that + # one writes to stdout/stderr and it's simpler to do it directly + # through subprocess. + return run_cmd(argv) + + +def _build_argv( + tool, + filename, + incldirs=None, + macros=None, + preargs=None, + postargs=None, + executable=None, + compiler=None, +): + compiler = distutils.ccompiler.new_compiler( + compiler=compiler or tool, + ) + if executable: + compiler.set_executable('preprocessor', executable) + + argv = None + def _spawn(_argv): + nonlocal argv + argv = _argv + compiler.spawn = _spawn + compiler.preprocess( + filename, + macros=[tuple(v) for v in macros or ()], + include_dirs=incldirs or (), + extra_preargs=preargs or (), + extra_postargs=postargs or (), + ) + return argv + + +@contextlib.contextmanager +def converted_error(tool, argv, filename): + try: + yield + except subprocess.CalledProcessError as exc: + convert_error( + tool, + argv, + filename, + exc.stderr, + exc.returncode, + ) + + +def convert_error(tool, argv, filename, stderr, rc): + error = (stderr.splitlines()[0], rc) + if (_expected := is_os_mismatch(filename, stderr)): + logger.debug(stderr.strip()) + raise OSMismatchError(filename, _expected, argv, error, tool) + elif (_missing := is_missing_dep(stderr)): + logger.debug(stderr.strip()) + raise MissingDependenciesError(filename, (_missing,), argv, error, tool) + elif '#error' in stderr: + # XXX Ignore incompatible files. + error = (stderr.splitlines()[1], rc) + logger.debug(stderr.strip()) + raise ErrorDirectiveError(filename, argv, error, tool) + else: + # Try one more time, with stderr written to the terminal. + try: + output = run_cmd(argv, stderr=None) + except subprocess.CalledProcessError: + raise PreprocessorFailure(filename, argv, error, tool) + + +def is_os_mismatch(filename, errtext=None): + # See: https://docs.python.org/3/library/sys.html#sys.platform + actual = sys.platform + if actual == 'unknown': + raise NotImplementedError + + if errtext is not None: + if (missing := is_missing_dep(errtext)): + matching = get_matching_oses(missing, filename) + if actual not in matching: + return matching + return False + + +def get_matching_oses(missing, filename): + # OSX + if 'darwin' in filename or 'osx' in filename: + return ('darwin',) + elif missing == 'SystemConfiguration/SystemConfiguration.h': + return ('darwin',) + + # Windows + elif missing in ('windows.h', 'winsock2.h'): + return ('win32',) + + # other + elif missing == 'sys/ldr.h': + return ('aix',) + elif missing == 'dl.h': + # XXX The existence of Python/dynload_dl.c implies others... + # Note that hpux isn't actual supported any more. + return ('hpux', '???') + + # unrecognized + else: + return () + + +def is_missing_dep(errtext): + if 'No such file or directory' in errtext: + missing = errtext.split(': No such file or directory')[0].split()[-1] + return missing + return False diff --git a/Tools/c-analyzer/c_parser/preprocessor/errors.py b/Tools/c-analyzer/c_parser/preprocessor/errors.py new file mode 100644 index 00000000000..9b66801d630 --- /dev/null +++ b/Tools/c-analyzer/c_parser/preprocessor/errors.py @@ -0,0 +1,110 @@ +import sys + + +OS = sys.platform + + +def _as_tuple(items): + if isinstance(items, str): + return tuple(items.strip().replace(',', ' ').split()) + elif items: + return tuple(items) + else: + return () + + +class PreprocessorError(Exception): + """Something preprocessor-related went wrong.""" + + @classmethod + def _msg(cls, filename, reason, **ignored): + msg = 'failure while preprocessing' + if reason: + msg = f'{msg} ({reason})' + return msg + + def __init__(self, filename, preprocessor=None, reason=None): + if isinstance(reason, str): + reason = reason.strip() + + self.filename = filename + self.preprocessor = preprocessor or None + self.reason = str(reason) if reason else None + + msg = self._msg(**vars(self)) + msg = f'({filename}) {msg}' + if preprocessor: + msg = f'[{preprocessor}] {msg}' + super().__init__(msg) + + +class PreprocessorFailure(PreprocessorError): + """The preprocessor command failed.""" + + @classmethod + def _msg(cls, error, **ignored): + msg = 'preprocessor command failed' + if error: + msg = f'{msg} {error}' + return msg + + def __init__(self, filename, argv, error=None, preprocessor=None): + exitcode = -1 + if isinstance(error, tuple): + if len(error) == 2: + error, exitcode = error + else: + error = str(error) + if isinstance(error, str): + error = error.strip() + + self.argv = _as_tuple(argv) or None + self.error = error if error else None + self.exitcode = exitcode + + reason = str(self.error) + super().__init__(filename, preprocessor, reason) + + +class ErrorDirectiveError(PreprocessorFailure): + """The file hit a #error directive.""" + + @classmethod + def _msg(cls, error, **ignored): + return f'#error directive hit ({error})' + + def __init__(self, filename, argv, error, *args, **kwargs): + super().__init__(filename, argv, error, *args, **kwargs) + + +class MissingDependenciesError(PreprocessorFailure): + """The preprocessor did not have access to all the target's dependencies.""" + + @classmethod + def _msg(cls, missing, **ignored): + msg = 'preprocessing failed due to missing dependencies' + if missing: + msg = f'{msg} ({", ".join(missing)})' + return msg + + def __init__(self, filename, missing=None, *args, **kwargs): + self.missing = _as_tuple(missing) or None + + super().__init__(filename, *args, **kwargs) + + +class OSMismatchError(MissingDependenciesError): + """The target is not compatible with the host OS.""" + + @classmethod + def _msg(cls, expected, **ignored): + return f'OS is {OS} but expected {expected or "???"}' + + def __init__(self, filename, expected=None, *args, **kwargs): + if isinstance(expected, str): + expected = expected.strip() + + self.actual = OS + self.expected = expected if expected else None + + super().__init__(filename, None, *args, **kwargs) diff --git a/Tools/c-analyzer/c_parser/preprocessor/gcc.py b/Tools/c-analyzer/c_parser/preprocessor/gcc.py new file mode 100644 index 00000000000..bb404a487b7 --- /dev/null +++ b/Tools/c-analyzer/c_parser/preprocessor/gcc.py @@ -0,0 +1,123 @@ +import os.path +import re + +from . import common as _common + + +TOOL = 'gcc' + +# https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html +LINE_MARKER_RE = re.compile(r'^# (\d+) "([^"]+)"(?: [1234])*$') +PREPROC_DIRECTIVE_RE = re.compile(r'^\s*#\s*(\w+)\b.*') +COMPILER_DIRECTIVE_RE = re.compile(r''' + ^ + (.*?) # <before> + (__\w+__) # <directive> + \s* + [(] [(] + ( + [^()]* + (?: + [(] + [^()]* + [)] + [^()]* + )* + ) # <args> + ( [)] [)] )? # <closed> +''', re.VERBOSE) + +POST_ARGS = ( + '-pthread', + '-std=c99', + #'-g', + #'-Og', + #'-Wno-unused-result', + #'-Wsign-compare', + #'-Wall', + #'-Wextra', + '-E', +) + + +def preprocess(filename, incldirs=None, macros=None, samefiles=None): + text = _common.preprocess( + TOOL, + filename, + incldirs=incldirs, + macros=macros, + #preargs=PRE_ARGS, + postargs=POST_ARGS, + executable=['gcc'], + compiler='unix', + ) + return _iter_lines(text, filename, samefiles) + + +def _iter_lines(text, filename, samefiles, *, raw=False): + lines = iter(text.splitlines()) + + # Build the lines and filter out directives. + partial = 0 # depth + origfile = None + for line in lines: + m = LINE_MARKER_RE.match(line) + if m: + lno, origfile = m.groups() + lno = int(lno) + elif _filter_orig_file(origfile, filename, samefiles): + if (m := PREPROC_DIRECTIVE_RE.match(line)): + name, = m.groups() + if name != 'pragma': + raise Exception(line) + else: + if not raw: + line, partial = _strip_directives(line, partial=partial) + yield _common.SourceLine( + _common.FileInfo(filename, lno), + 'source', + line or '', + None, + ) + lno += 1 + + +def _strip_directives(line, partial=0): + # We assume there are no string literals with parens in directive bodies. + while partial > 0: + if not (m := re.match(r'[^{}]*([()])', line)): + return None, partial + delim, = m.groups() + partial += 1 if delim == '(' else -1 # opened/closed + line = line[m.end():] + + line = re.sub(r'__extension__', '', line) + + while (m := COMPILER_DIRECTIVE_RE.match(line)): + before, _, _, closed = m.groups() + if closed: + line = f'{before} {line[m.end():]}' + else: + after, partial = _strip_directives(line[m.end():], 2) + line = f'{before} {after or ""}' + if partial: + break + + return line, partial + + +def _filter_orig_file(origfile, current, samefiles): + if origfile == current: + return True + if origfile == '<stdin>': + return True + if os.path.isabs(origfile): + return False + + for filename in samefiles or (): + if filename.endswith(os.path.sep): + filename += os.path.basename(current) + if origfile == filename: + return True + + return False diff --git a/Tools/c-analyzer/c_parser/preprocessor/pure.py b/Tools/c-analyzer/c_parser/preprocessor/pure.py new file mode 100644 index 00000000000..e971389b188 --- /dev/null +++ b/Tools/c-analyzer/c_parser/preprocessor/pure.py @@ -0,0 +1,23 @@ +from ..source import ( + opened as _open_source, +) +from . import common as _common + + +def preprocess(lines, filename=None): + if isinstance(lines, str): + with _open_source(lines, filename) as (lines, filename): + yield from preprocess(lines, filename) + return + + # XXX actually preprocess... + for lno, line in enumerate(lines, 1): + kind = 'source' + data = line + conditions = None + yield _common.SourceLine( + _common.FileInfo(filename, lno), + kind, + data, + conditions, + ) diff --git a/Tools/c-analyzer/c_parser/source.py b/Tools/c-analyzer/c_parser/source.py new file mode 100644 index 00000000000..30a09eeb56a --- /dev/null +++ b/Tools/c-analyzer/c_parser/source.py @@ -0,0 +1,64 @@ +import contextlib +import os.path + + +def resolve(source, filename): + if _looks_like_filename(source): + return _resolve_filename(source, filename) + + if isinstance(source, str): + source = source.splitlines() + + # At this point "source" is not a str. + if not filename: + filename = None + elif not isinstance(filename, str): + raise TypeError(f'filename should be str (or None), got {filename!r}') + else: + filename, _ = _resolve_filename(filename) + return source, filename + + +@contextlib.contextmanager +def good_file(filename, alt=None): + if not _looks_like_filename(filename): + raise ValueError(f'expected a filename, got {filename}') + filename, _ = _resolve_filename(filename, alt) + try: + yield filename + except Exception: + if not os.path.exists(filename): + raise FileNotFoundError(f'file not found: {filename}') + raise # re-raise + + +def _looks_like_filename(value): + if not isinstance(value, str): + return False + return value.endswith(('.c', '.h')) + + +def _resolve_filename(filename, alt=None): + if os.path.isabs(filename): + ... +# raise NotImplementedError + else: + filename = os.path.join('.', filename) + + if not alt: + alt = filename + elif os.path.abspath(filename) == os.path.abspath(alt): + alt = filename + else: + raise ValueError(f'mismatch: {filename} != {alt}') + return filename, alt + + +@contextlib.contextmanager +def opened(source, filename=None): + source, filename = resolve(source, filename) + if isinstance(source, str): + with open(source) as srcfile: + yield srcfile, filename + else: + yield source, filename diff --git a/Tools/c-analyzer/check-c-globals.py b/Tools/c-analyzer/check-c-globals.py index 1371f927423..3fe2bdcae14 100644 --- a/Tools/c-analyzer/check-c-globals.py +++ b/Tools/c-analyzer/check-c-globals.py @@ -1,448 +1,35 @@ +from cpython.__main__ import main, configure_logger -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( - glob.escape(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/Python-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:] +def parse_args(): import argparse + from c_common.scriptutil import ( + add_verbosity_cli, + add_traceback_cli, + process_args_by_key, + ) + from cpython.__main__ import _cli_check parser = argparse.ArgumentParser() + processors = [ + add_verbosity_cli(parser), + add_traceback_cli(parser), + _cli_check(parser, checks='<globals>'), + ] - 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) + args = parser.parse_args() + ns = vars(args) - total = 0 - for line, count in lines: - total += count - log(line) - log('\ntotal: {}'.format(total)) + cmd = 'check' + verbosity, traceback_cm = process_args_by_key( + args, + processors, + ['verbosity', 'traceback_cm'], + ) - if total and rc: - print('ERROR: found unsafe globals', file=sys.stderr) - return rc - return 0 + return cmd, ns, verbosity, traceback_cm -if __name__ == '__main__': - args = parse_args() - sys.exit( - main(**vars(args))) +(cmd, cmd_kwargs, verbosity, traceback_cm) = parse_args() +configure_logger(verbosity) +with traceback_cm: + main(cmd, cmd_kwargs) diff --git a/Tools/c-analyzer/cpython/README b/Tools/c-analyzer/cpython/README deleted file mode 100644 index 772b8be2700..00000000000 --- a/Tools/c-analyzer/cpython/README +++ /dev/null @@ -1,72 +0,0 @@ -####################################### -# C Globals and CPython Runtime State. - -CPython's C code makes extensive use of global variables (whether static -globals or static locals). Each such variable falls into one of several -categories: - -* strictly const data -* used exclusively in main or in the REPL -* process-global state (e.g. managing process-level resources - like signals and file descriptors) -* Python "global" runtime state -* per-interpreter runtime state - -The last one can be a problem as soon as anyone creates a second -interpreter (AKA "subinterpreter") in a process. It is definitely a -problem under subinterpreters if they are no longer sharing the GIL, -since the GIL protects us from a lot of race conditions. Keep in mind -that ultimately *all* objects (PyObject) should be treated as -per-interpreter state. This includes "static types", freelists, -_PyIdentifier, and singletons. Take that in for a second. It has -significant implications on where we use static variables! - -Be aware that module-global state (stored in C statics) is a kind of -per-interpreter state. There have been efforts across many years, and -still going, to provide extension module authors mechanisms to store -that state safely (see PEPs 3121, 489, etc.). - -(Note that there has been discussion around support for running multiple -Python runtimes in the same process. That would ends up with the same -problems, relative to static variables, that subinterpreters have.) - -Historically we have been bad at keeping per-interpreter state out of -static variables, mostly because until recently subinterpreters were -not widely used nor even factored in to solutions. However, the -feature is growing in popularity and use in the community. - -Mandate: "Eliminate use of static variables for per-interpreter state." - -The "c-statics.py" script in this directory, along with its accompanying -data files, are part of the effort to resolve existing problems with -our use of static variables and to prevent future problems. - -#------------------------- -## statics for actually-global state (and runtime state consolidation) - -In general, holding any kind of state in static variables -increases maintenance burden and increases the complexity of code (e.g. -we use TSS to identify the active thread state). So it is a good idea -to avoid using statics for state even if for the "global" runtime or -for process-global state. - -Relative to maintenance burden, one problem is where the runtime -state is spread throughout 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 tools in this directory -are run as part of the test suite to ensure that no new globals have -been added. The script can be run manually as well: - - ./python Lib/test/test_c_statics/c-statics.py check - -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 marked as ignored. diff --git a/Tools/c-analyzer/cpython/__init__.py b/Tools/c-analyzer/cpython/__init__.py index ae45b424e3c..d0b3eff3c4b 100644 --- a/Tools/c-analyzer/cpython/__init__.py +++ b/Tools/c-analyzer/cpython/__init__.py @@ -1,29 +1,20 @@ import os.path -import sys -TOOL_ROOT = os.path.abspath( +TOOL_ROOT = os.path.normcase( + os.path.abspath( os.path.dirname( # c-analyzer/ - os.path.dirname(__file__))) # cpython/ -DATA_DIR = TOOL_ROOT + os.path.dirname(__file__)))) # cpython/ REPO_ROOT = ( os.path.dirname( # .. os.path.dirname(TOOL_ROOT))) # Tools/ INCLUDE_DIRS = [os.path.join(REPO_ROOT, name) for name in [ - 'Include', - ]] + 'Include', +]] SOURCE_DIRS = [os.path.join(REPO_ROOT, name) for name in [ - 'Python', - 'Parser', - 'Objects', - 'Modules', - ]] - -#PYTHON = os.path.join(REPO_ROOT, 'python') -PYTHON = sys.executable - - -# Clean up the namespace. -del sys -del os + 'Python', + 'Parser', + 'Objects', + 'Modules', +]] diff --git a/Tools/c-analyzer/cpython/__main__.py b/Tools/c-analyzer/cpython/__main__.py index 6b0f9bcb968..23a3de06f63 100644 --- a/Tools/c-analyzer/cpython/__main__.py +++ b/Tools/c-analyzer/cpython/__main__.py @@ -1,212 +1,280 @@ -import argparse -import re +import logging import sys -from c_analyzer.common import show -from c_analyzer.common.info import UNKNOWN +from c_common.fsutil import expand_filenames, iter_files_by_suffix +from c_common.scriptutil import ( + add_verbosity_cli, + add_traceback_cli, + add_commands_cli, + add_kind_filtering_cli, + add_files_cli, + process_args_by_key, + configure_logger, + get_prog, +) +from c_parser.info import KIND +import c_parser.__main__ as c_parser +import c_analyzer.__main__ as c_analyzer +import c_analyzer as _c_analyzer +from c_analyzer.info import UNKNOWN +from . import _analyzer, _parser, REPO_ROOT + + +logger = logging.getLogger(__name__) + + +def _resolve_filenames(filenames): + if filenames: + resolved = (_parser.resolve_filename(f) for f in filenames) + else: + resolved = _parser.iter_filenames() + return resolved + + +def fmt_summary(analysis): + # XXX Support sorting and grouping. + supported = [] + unsupported = [] + for item in analysis: + if item.supported: + supported.append(item) + else: + unsupported.append(item) + total = 0 + + def section(name, groupitems): + nonlocal total + items, render = c_analyzer.build_section(name, groupitems, + relroot=REPO_ROOT) + yield from render() + total += len(items) + + yield '' + yield '====================' + yield 'supported' + yield '====================' + + yield from section('types', supported) + yield from section('variables', supported) + + yield '' + yield '====================' + yield 'unsupported' + yield '====================' + + yield from section('types', unsupported) + yield from section('variables', unsupported) + + yield '' + yield f'grand total: {total}' + -from . import SOURCE_DIRS -from .find import supported_vars -from .known import ( - from_file as known_from_file, - DATA_FILE as KNOWN_FILE, +####################################### +# the checks + +CHECKS = dict(c_analyzer.CHECKS, **{ + 'globals': _analyzer.check_globals, +}) + +####################################### +# the commands + +FILES_KWARGS = dict(excluded=_parser.EXCLUDED, nargs='*') + + +def _cli_parse(parser): + process_output = c_parser.add_output_cli(parser) + process_kind = add_kind_filtering_cli(parser) + process_preprocessor = c_parser.add_preprocessor_cli( + parser, + get_preprocessor=_parser.get_preprocessor, + ) + process_files = add_files_cli(parser, **FILES_KWARGS) + return [ + process_output, + process_kind, + process_preprocessor, + process_files, + ] + + +def cmd_parse(filenames=None, **kwargs): + filenames = _resolve_filenames(filenames) + if 'get_file_preprocessor' not in kwargs: + kwargs['get_file_preprocessor'] = _parser.get_preprocessor() + c_parser.cmd_parse(filenames, **kwargs) + + +def _cli_check(parser, **kwargs): + return c_analyzer._cli_check(parser, CHECKS, **kwargs, **FILES_KWARGS) + + +def cmd_check(filenames=None, **kwargs): + filenames = _resolve_filenames(filenames) + kwargs['get_file_preprocessor'] = _parser.get_preprocessor(log_err=print) + c_analyzer.cmd_check( + filenames, + relroot=REPO_ROOT, + _analyze=_analyzer.analyze, + _CHECKS=CHECKS, + **kwargs ) -from .supported import IGNORED_FILE - - -def _check_results(unknown, knownvars, used): - def _match_unused_global(variable): - found = [] - for varid in knownvars: - if varid in used: - continue - if varid.funcname is not None: - continue - if varid.name != variable.name: - continue - if variable.filename and variable.filename != UNKNOWN: - if variable.filename == varid.filename: - found.append(varid) - else: - found.append(varid) - return found - - badknown = set() - for variable in sorted(unknown): - msg = None - if variable.funcname != UNKNOWN: - msg = f'could not find global symbol {variable.id}' - elif m := _match_unused_global(variable): - assert isinstance(m, list) - badknown.update(m) - elif variable.name in ('completed', 'id'): # XXX Figure out where these variables are. - unknown.remove(variable) - else: - msg = f'could not find local symbol {variable.id}' - if msg: - #raise Exception(msg) - print(msg) - if badknown: - print('---') - print(f'{len(badknown)} globals in known.tsv, but may actually be local:') - for varid in sorted(badknown): - print(f'{varid.filename:30} {varid.name}') - unused = sorted(varid - for varid in set(knownvars) - used - if varid.name != 'id') # XXX Figure out where these variables are. - if unused: - print('---') - print(f'did not use {len(unused)} known vars:') - for varid in unused: - print(f'{varid.filename:30} {varid.funcname or "-":20} {varid.name}') - raise Exception('not all known symbols used') - if unknown: - print('---') - raise Exception('could not find all symbols') - - -# XXX Move this check to its own command. -def cmd_check_cache(cmd, *, - known=KNOWN_FILE, - ignored=IGNORED_FILE, - _known_from_file=known_from_file, - _find=supported_vars, - ): - known = _known_from_file(known) - - used = set() - unknown = set() - for var, supported in _find(known=known, ignored=ignored): - if supported is None: - unknown.add(var) - continue - used.add(var.id) - _check_results(unknown, known['variables'], used) - - -def cmd_check(cmd, *, - known=KNOWN_FILE, - ignored=IGNORED_FILE, - _find=supported_vars, - _show=show.basic, - _print=print, - ): - """ - Fail if there are unsupported globals variables. - - In the failure case, the list of unsupported variables - will be printed out. - """ - unsupported = [] - for var, supported in _find(known=known, ignored=ignored): - if not supported: - unsupported.append(var) - - if not unsupported: - #_print('okay') - return - - _print('ERROR: found unsupported global variables') - _print() - _show(sorted(unsupported)) - _print(f' ({len(unsupported)} total)') - sys.exit(1) - - -def cmd_show(cmd, *, - known=KNOWN_FILE, - ignored=IGNORED_FILE, - skip_objects=False, - _find=supported_vars, - _show=show.basic, - _print=print, - ): - """ - Print out the list of found global variables. - - The variables will be distinguished as "supported" or "unsupported". - """ - allsupported = [] - allunsupported = [] - for found, supported in _find(known=known, - ignored=ignored, - skip_objects=skip_objects, - ): - if supported is None: - continue - (allsupported if supported else allunsupported - ).append(found) - - _print('supported:') - _print('----------') - _show(sorted(allsupported)) - _print(f' ({len(allsupported)} total)') - _print() - _print('unsupported:') - _print('------------') - _show(sorted(allunsupported)) - _print(f' ({len(allunsupported)} total)') - - -############################# -# the script -COMMANDS = { - 'check': cmd_check, - 'show': cmd_show, - } - -PROG = sys.argv[0] -PROG = 'c-globals.py' - - -def parse_args(prog=PROG, argv=sys.argv[1:], *, _fail=None): - common = argparse.ArgumentParser(add_help=False) - common.add_argument('--ignored', metavar='FILE', - default=IGNORED_FILE, - help='path to file that lists ignored vars') - common.add_argument('--known', metavar='FILE', - default=KNOWN_FILE, - help='path to file that lists known types') - #common.add_argument('dirs', metavar='DIR', nargs='*', - # default=SOURCE_DIRS, - # help='a directory to check') - parser = argparse.ArgumentParser( - prog=prog, +def cmd_analyze(filenames=None, **kwargs): + formats = dict(c_analyzer.FORMATS) + formats['summary'] = fmt_summary + filenames = _resolve_filenames(filenames) + kwargs['get_file_preprocessor'] = _parser.get_preprocessor(log_err=print) + c_analyzer.cmd_analyze( + filenames, + _analyze=_analyzer.analyze, + formats=formats, + **kwargs + ) + + +def _cli_data(parser): + filenames = False + known = True + return c_analyzer._cli_data(parser, filenames, known) + + +def cmd_data(datacmd, **kwargs): + formats = dict(c_analyzer.FORMATS) + formats['summary'] = fmt_summary + filenames = (file + for file in _resolve_filenames(None) + if file not in _parser.EXCLUDED) + kwargs['get_file_preprocessor'] = _parser.get_preprocessor(log_err=print) + if datacmd == 'show': + types = _analyzer.read_known() + results = [] + for decl, info in types.items(): + if info is UNKNOWN: + if decl.kind in (KIND.STRUCT, KIND.UNION): + extra = {'unsupported': ['type unknown'] * len(decl.members)} + else: + extra = {'unsupported': ['type unknown']} + info = (info, extra) + results.append((decl, info)) + if decl.shortkey == 'struct _object': + tempinfo = info + known = _analyzer.Analysis.from_results(results) + analyze = None + elif datacmd == 'dump': + known = _analyzer.KNOWN_FILE + def analyze(files, **kwargs): + decls = [] + for decl in _analyzer.iter_decls(files, **kwargs): + if not KIND.is_type_decl(decl.kind): + continue + if not decl.filename.endswith('.h'): + if decl.shortkey not in _analyzer.KNOWN_IN_DOT_C: + continue + decls.append(decl) + results = _c_analyzer.analyze_decls( + decls, + known={}, + analyze_resolved=_analyzer.analyze_resolved, ) - subs = parser.add_subparsers(dest='cmd') + return _analyzer.Analysis.from_results(results) + else: + known = _analyzer.read_known() + def analyze(files, **kwargs): + return _analyzer.iter_decls(files, **kwargs) + extracolumns = None + c_analyzer.cmd_data( + datacmd, + filenames, + known, + _analyze=analyze, + formats=formats, + extracolumns=extracolumns, + relroot=REPO_ROOT, + **kwargs + ) - check = subs.add_parser('check', parents=[common]) - show = subs.add_parser('show', parents=[common]) - show.add_argument('--skip-objects', action='store_true') +# We do not define any other cmd_*() handlers here, +# favoring those defined elsewhere. - if _fail is None: - def _fail(msg): - parser.error(msg) +COMMANDS = { + 'check': ( + 'analyze and fail if the CPython source code has any problems', + [_cli_check], + cmd_check, + ), + 'analyze': ( + 'report on the state of the CPython source code', + [(lambda p: c_analyzer._cli_analyze(p, **FILES_KWARGS))], + cmd_analyze, + ), + 'parse': ( + 'parse the CPython source files', + [_cli_parse], + cmd_parse, + ), + 'data': ( + 'check/manage local data (e.g. knwon types, ignored vars, caches)', + [_cli_data], + cmd_data, + ), +} + + +####################################### +# the script + +def parse_args(argv=sys.argv[1:], prog=None, *, subset=None): + import argparse + parser = argparse.ArgumentParser( + prog=prog or get_prog(), + ) + +# if subset == 'check' or subset == ['check']: +# if checks is not None: +# commands = dict(COMMANDS) +# commands['check'] = list(commands['check']) +# cli = commands['check'][1][0] +# commands['check'][1][0] = (lambda p: cli(p, checks=checks)) + processors = add_commands_cli( + parser, + commands=COMMANDS, + commonspecs=[ + add_verbosity_cli, + add_traceback_cli, + ], + subset=subset, + ) - # Now parse the args. args = parser.parse_args(argv) ns = vars(args) cmd = ns.pop('cmd') - if not cmd: - _fail('missing command') - return cmd, ns + verbosity, traceback_cm = process_args_by_key( + args, + processors[cmd], + ['verbosity', 'traceback_cm'], + ) + if cmd != 'parse': + # "verbosity" is sent to the commands, so we put it back. + args.verbosity = verbosity + + return cmd, ns, verbosity, traceback_cm -def main(cmd, cmdkwargs=None, *, _COMMANDS=COMMANDS): +def main(cmd, cmd_kwargs): try: - cmdfunc = _COMMANDS[cmd] + run_cmd = COMMANDS[cmd][-1] except KeyError: - raise ValueError( - f'unsupported cmd {cmd!r}' if cmd else 'missing cmd') - - cmdfunc(cmd, **cmdkwargs or {}) + raise ValueError(f'unsupported cmd {cmd!r}') + run_cmd(**cmd_kwargs) if __name__ == '__main__': - cmd, cmdkwargs = parse_args() - main(cmd, cmdkwargs) + cmd, cmd_kwargs, verbosity, traceback_cm = parse_args() + configure_logger(verbosity) + with traceback_cm: + main(cmd, cmd_kwargs) diff --git a/Tools/c-analyzer/cpython/_analyzer.py b/Tools/c-analyzer/cpython/_analyzer.py new file mode 100644 index 00000000000..98f8888651e --- /dev/null +++ b/Tools/c-analyzer/cpython/_analyzer.py @@ -0,0 +1,348 @@ +import os.path +import re + +from c_common.clsutil import classonly +from c_parser.info import ( + KIND, + DeclID, + Declaration, + TypeDeclaration, + TypeDef, + Struct, + Member, + FIXED_TYPE, + is_type_decl, + is_pots, + is_funcptr, + is_process_global, + is_fixed_type, + is_immutable, +) +import c_analyzer as _c_analyzer +import c_analyzer.info as _info +import c_analyzer.datafiles as _datafiles +from . import _parser, REPO_ROOT + + +_DATA_DIR = os.path.dirname(__file__) +KNOWN_FILE = os.path.join(_DATA_DIR, 'known.tsv') +IGNORED_FILE = os.path.join(_DATA_DIR, 'ignored.tsv') +KNOWN_IN_DOT_C = { + 'struct _odictobject': False, + 'PyTupleObject': False, + 'struct _typeobject': False, + 'struct _arena': True, # ??? + 'struct _frame': False, + 'struct _ts': True, # ??? + 'struct PyCodeObject': False, + 'struct _is': True, # ??? + 'PyWideStringList': True, # ??? + # recursive + 'struct _dictkeysobject': False, +} +# These are loaded from the respective .tsv files upon first use. +_KNOWN = { + # {(file, ID) | ID => info | bool} + #'PyWideStringList': True, +} +#_KNOWN = {(Struct(None, typeid.partition(' ')[-1], None) +# if typeid.startswith('struct ') +# else TypeDef(None, typeid, None) +# ): ([], {'unsupported': None if supported else True}) +# for typeid, supported in _KNOWN_IN_DOT_C.items()} +_IGNORED = { + # {ID => reason} +} + +KINDS = frozenset((*KIND.TYPES, KIND.VARIABLE)) + + +def read_known(): + if not _KNOWN: + # Cache a copy the first time. + extracols = None # XXX + #extracols = ['unsupported'] + known = _datafiles.read_known(KNOWN_FILE, extracols, REPO_ROOT) + # For now we ignore known.values() (i.e. "extra"). + types, _ = _datafiles.analyze_known( + known, + analyze_resolved=analyze_resolved, + ) + _KNOWN.update(types) + return _KNOWN.copy() + + +def write_known(): + raise NotImplementedError + datafiles.write_known(decls, IGNORED_FILE, ['unsupported'], relroot=REPO_ROOT) + + +def read_ignored(): + if not _IGNORED: + _IGNORED.update(_datafiles.read_ignored(IGNORED_FILE)) + return dict(_IGNORED) + + +def write_ignored(): + raise NotImplementedError + datafiles.write_ignored(variables, IGNORED_FILE) + + +def analyze(filenames, *, + skip_objects=False, + **kwargs + ): + if skip_objects: + # XXX Set up a filter. + raise NotImplementedError + + known = read_known() + + decls = iter_decls(filenames) + results = _c_analyzer.analyze_decls( + decls, + known, + analyze_resolved=analyze_resolved, + ) + analysis = Analysis.from_results(results) + + return analysis + + +def iter_decls(filenames, **kwargs): + decls = _c_analyzer.iter_decls( + filenames, + # We ignore functions (and statements). + kinds=KINDS, + parse_files=_parser.parse_files, + **kwargs + ) + for decl in decls: + if not decl.data: + # Ignore forward declarations. + continue + yield decl + + +def analyze_resolved(resolved, decl, types, knowntypes, extra=None): + if decl.kind not in KINDS: + # Skip it! + return None + + typedeps = resolved + if typedeps is _info.UNKNOWN: + if decl.kind in (KIND.STRUCT, KIND.UNION): + typedeps = [typedeps] * len(decl.members) + else: + typedeps = [typedeps] + #assert isinstance(typedeps, (list, TypeDeclaration)), typedeps + + if extra is None: + extra = {} + elif 'unsupported' in extra: + raise NotImplementedError((decl, extra)) + + unsupported = _check_unsupported(decl, typedeps, types, knowntypes) + extra['unsupported'] = unsupported + + return typedeps, extra + + +def _check_unsupported(decl, typedeps, types, knowntypes): + if typedeps is None: + raise NotImplementedError(decl) + + if decl.kind in (KIND.STRUCT, KIND.UNION): + return _check_members(decl, typedeps, types, knowntypes) + elif decl.kind is KIND.ENUM: + if typedeps: + raise NotImplementedError((decl, typedeps)) + return None + else: + return _check_typedep(decl, typedeps, types, knowntypes) + + +def _check_members(decl, typedeps, types, knowntypes): + if isinstance(typedeps, TypeDeclaration): + raise NotImplementedError((decl, typedeps)) + + #members = decl.members or () # A forward decl has no members. + members = decl.members + if not members: + # A forward decl has no members, but that shouldn't surface here.. + raise NotImplementedError(decl) + if len(members) != len(typedeps): + raise NotImplementedError((decl, typedeps)) + + unsupported = [] + for member, typedecl in zip(members, typedeps): + checked = _check_typedep(member, typedecl, types, knowntypes) + unsupported.append(checked) + if any(None if v is FIXED_TYPE else v for v in unsupported): + return unsupported + elif FIXED_TYPE in unsupported: + return FIXED_TYPE + else: + return None + + +def _check_typedep(decl, typedecl, types, knowntypes): + if not isinstance(typedecl, TypeDeclaration): + if hasattr(type(typedecl), '__len__'): + if len(typedecl) == 1: + typedecl, = typedecl + if typedecl is None: + # XXX Fail? + return 'typespec (missing)' + elif typedecl is _info.UNKNOWN: + # XXX Is this right? + return 'typespec (unknown)' + elif not isinstance(typedecl, TypeDeclaration): + raise NotImplementedError((decl, typedecl)) + + if isinstance(decl, Member): + return _check_vartype(decl, typedecl, types, knowntypes) + elif not isinstance(decl, Declaration): + raise NotImplementedError(decl) + elif decl.kind is KIND.TYPEDEF: + return _check_vartype(decl, typedecl, types, knowntypes) + elif decl.kind is KIND.VARIABLE: + if not is_process_global(decl): + return None + checked = _check_vartype(decl, typedecl, types, knowntypes) + return 'mutable' if checked is FIXED_TYPE else checked + else: + raise NotImplementedError(decl) + + +def _check_vartype(decl, typedecl, types, knowntypes): + """Return failure reason.""" + checked = _check_typespec(decl, typedecl, types, knowntypes) + if checked: + return checked + if is_immutable(decl.vartype): + return None + if is_fixed_type(decl.vartype): + return FIXED_TYPE + return 'mutable' + + +def _check_typespec(decl, typedecl, types, knowntypes): + typespec = decl.vartype.typespec + if typedecl is not None: + found = types.get(typedecl) + if found is None: + found = knowntypes.get(typedecl) + + if found is not None: + _, extra = found + if extra is None: + # XXX Under what circumstances does this happen? + extra = {} + unsupported = extra.get('unsupported') + if unsupported is FIXED_TYPE: + unsupported = None + return 'typespec' if unsupported else None + # Fall back to default known types. + if is_pots(typespec): + return None + elif _info.is_system_type(typespec): + return None + elif is_funcptr(decl.vartype): + return None + return 'typespec' + + +class Analyzed(_info.Analyzed): + + @classonly + def is_target(cls, raw): + if not super().is_target(raw): + return False + if raw.kind not in KINDS: + return False + return True + + #@classonly + #def _parse_raw_result(cls, result, extra): + # typedecl, extra = super()._parse_raw_result(result, extra) + # if typedecl is None: + # return None, extra + # raise NotImplementedError + + def __init__(self, item, typedecl=None, *, unsupported=None, **extra): + if 'unsupported' in extra: + raise NotImplementedError((item, typedecl, unsupported, extra)) + if not unsupported: + unsupported = None + elif isinstance(unsupported, (str, TypeDeclaration)): + unsupported = (unsupported,) + elif unsupported is not FIXED_TYPE: + unsupported = tuple(unsupported) + self.unsupported = unsupported + extra['unsupported'] = self.unsupported # ...for __repr__(), etc. + if self.unsupported is None: + #self.supported = None + self.supported = True + elif self.unsupported is FIXED_TYPE: + if item.kind is KIND.VARIABLE: + raise NotImplementedError(item, typedecl, unsupported) + self.supported = True + else: + self.supported = not self.unsupported + super().__init__(item, typedecl, **extra) + + def render(self, fmt='line', *, itemonly=False): + if fmt == 'raw': + yield repr(self) + return + rendered = super().render(fmt, itemonly=itemonly) + # XXX ??? + #if itemonly: + # yield from rendered + supported = self._supported + if fmt in ('line', 'brief'): + rendered, = rendered + parts = [ + '+' if supported else '-' if supported is False else '', + rendered, + ] + yield '\t'.join(parts) + elif fmt == 'summary': + raise NotImplementedError(fmt) + elif fmt == 'full': + yield from rendered + if supported: + yield f'\tsupported:\t{supported}' + else: + raise NotImplementedError(fmt) + + +class Analysis(_info.Analysis): + _item_class = Analyzed + + @classonly + def build_item(cls, info, result=None): + if not isinstance(info, Declaration) or info.kind not in KINDS: + raise NotImplementedError((info, result)) + return super().build_item(info, result) + + +def check_globals(analysis): + # yield (data, failure) + ignored = read_ignored() + for item in analysis: + if item.kind != KIND.VARIABLE: + continue + if item.supported: + continue + if item.id in ignored: + continue + reason = item.unsupported + if not reason: + reason = '???' + elif not isinstance(reason, str): + if len(reason) == 1: + reason, = reason + reason = f'({reason})' + yield item, f'not supported {reason:20}\t{item.storage or ""} {item.vartype}' diff --git a/Tools/c-analyzer/cpython/_generate.py b/Tools/c-analyzer/cpython/_generate.py deleted file mode 100644 index 3456604b814..00000000000 --- a/Tools/c-analyzer/cpython/_generate.py +++ /dev/null @@ -1,326 +0,0 @@ -# The code here consists of hacks for pre-populating the known.tsv file. - -from c_analyzer.parser.preprocessor import _iter_clean_lines -from c_analyzer.parser.naive import ( - iter_variables, parse_variable_declaration, find_variables, - ) -from c_analyzer.common.known import HEADER as KNOWN_HEADER -from c_analyzer.common.info import UNKNOWN, ID -from c_analyzer.variables import Variable -from c_analyzer.util import write_tsv - -from . import SOURCE_DIRS, REPO_ROOT -from .known import DATA_FILE as KNOWN_FILE -from .files import iter_cpython_files - - -POTS = ('char ', 'wchar_t ', 'int ', 'Py_ssize_t ') -POTS += tuple('const ' + v for v in POTS) -STRUCTS = ('PyTypeObject', 'PyObject', 'PyMethodDef', 'PyModuleDef', 'grammar') - - -def _parse_global(line, funcname=None): - line = line.strip() - if line.startswith('static '): - if '(' in line and '[' not in line and ' = ' not in line: - return None, None - name, decl = parse_variable_declaration(line) - elif line.startswith(('Py_LOCAL(', 'Py_LOCAL_INLINE(')): - name, decl = parse_variable_declaration(line) - elif line.startswith('_Py_static_string('): - decl = line.strip(';').strip() - name = line.split('(')[1].split(',')[0].strip() - elif line.startswith('_Py_IDENTIFIER('): - decl = line.strip(';').strip() - name = 'PyId_' + line.split('(')[1].split(')')[0].strip() - elif funcname: - return None, None - - # global-only - elif line.startswith('PyAPI_DATA('): # only in .h files - name, decl = parse_variable_declaration(line) - elif line.startswith('extern '): # only in .h files - name, decl = parse_variable_declaration(line) - elif line.startswith('PyDoc_VAR('): - decl = line.strip(';').strip() - name = line.split('(')[1].split(')')[0].strip() - elif line.startswith(POTS): # implied static - if '(' in line and '[' not in line and ' = ' not in line: - return None, None - name, decl = parse_variable_declaration(line) - elif line.startswith(STRUCTS) and line.endswith(' = {'): # implied static - name, decl = parse_variable_declaration(line) - elif line.startswith(STRUCTS) and line.endswith(' = NULL;'): # implied static - name, decl = parse_variable_declaration(line) - elif line.startswith('struct '): - if not line.endswith(' = {'): - return None, None - if not line.partition(' ')[2].startswith(STRUCTS): - return None, None - # implied static - name, decl = parse_variable_declaration(line) - - # file-specific - elif line.startswith(('SLOT1BINFULL(', 'SLOT1BIN(')): - # Objects/typeobject.c - funcname = line.split('(')[1].split(',')[0] - return [ - ('op_id', funcname, '_Py_static_string(op_id, OPSTR)'), - ('rop_id', funcname, '_Py_static_string(op_id, OPSTR)'), - ] - elif line.startswith('WRAP_METHOD('): - # Objects/weakrefobject.c - funcname, name = (v.strip() for v in line.split('(')[1].split(')')[0].split(',')) - return [ - ('PyId_' + name, funcname, f'_Py_IDENTIFIER({name})'), - ] - - else: - return None, None - return name, decl - - -def _pop_cached(varcache, filename, funcname, name, *, - _iter_variables=iter_variables, - ): - # Look for the file. - try: - cached = varcache[filename] - except KeyError: - cached = varcache[filename] = {} - for variable in _iter_variables(filename, - parse_variable=_parse_global, - ): - variable._isglobal = True - cached[variable.id] = variable - for var in cached: - print(' ', var) - - # Look for the variable. - if funcname == UNKNOWN: - for varid in cached: - if varid.name == name: - break - else: - return None - return cached.pop(varid) - else: - return cached.pop((filename, funcname, name), None) - - -def find_matching_variable(varid, varcache, allfilenames, *, - _pop_cached=_pop_cached, - ): - if varid.filename and varid.filename != UNKNOWN: - filenames = [varid.filename] - else: - filenames = allfilenames - for filename in filenames: - variable = _pop_cached(varcache, filename, varid.funcname, varid.name) - if variable is not None: - return variable - else: - if varid.filename and varid.filename != UNKNOWN and varid.funcname is None: - for filename in allfilenames: - if not filename.endswith('.h'): - continue - variable = _pop_cached(varcache, filename, None, varid.name) - if variable is not None: - return variable - return None - - -MULTILINE = { - # Python/Python-ast.c - 'Load_singleton': 'PyObject *', - 'Store_singleton': 'PyObject *', - 'Del_singleton': 'PyObject *', - 'AugLoad_singleton': 'PyObject *', - 'AugStore_singleton': 'PyObject *', - 'Param_singleton': 'PyObject *', - 'And_singleton': 'PyObject *', - 'Or_singleton': 'PyObject *', - 'Add_singleton': 'static PyObject *', - 'Sub_singleton': 'static PyObject *', - 'Mult_singleton': 'static PyObject *', - 'MatMult_singleton': 'static PyObject *', - 'Div_singleton': 'static PyObject *', - 'Mod_singleton': 'static PyObject *', - 'Pow_singleton': 'static PyObject *', - 'LShift_singleton': 'static PyObject *', - 'RShift_singleton': 'static PyObject *', - 'BitOr_singleton': 'static PyObject *', - 'BitXor_singleton': 'static PyObject *', - 'BitAnd_singleton': 'static PyObject *', - 'FloorDiv_singleton': 'static PyObject *', - 'Invert_singleton': 'static PyObject *', - 'Not_singleton': 'static PyObject *', - 'UAdd_singleton': 'static PyObject *', - 'USub_singleton': 'static PyObject *', - 'Eq_singleton': 'static PyObject *', - 'NotEq_singleton': 'static PyObject *', - 'Lt_singleton': 'static PyObject *', - 'LtE_singleton': 'static PyObject *', - 'Gt_singleton': 'static PyObject *', - 'GtE_singleton': 'static PyObject *', - 'Is_singleton': 'static PyObject *', - 'IsNot_singleton': 'static PyObject *', - 'In_singleton': 'static PyObject *', - 'NotIn_singleton': 'static PyObject *', - # Python/symtable.c - 'top': 'static identifier ', - 'lambda': 'static identifier ', - 'genexpr': 'static identifier ', - 'listcomp': 'static identifier ', - 'setcomp': 'static identifier ', - 'dictcomp': 'static identifier ', - '__class__': 'static identifier ', - # Python/compile.c - '__doc__': 'static PyObject *', - '__annotations__': 'static PyObject *', - # Objects/floatobject.c - 'double_format': 'static float_format_type ', - 'float_format': 'static float_format_type ', - 'detected_double_format': 'static float_format_type ', - 'detected_float_format': 'static float_format_type ', - # Python/dtoa.c - 'private_mem': 'static double private_mem[PRIVATE_mem]', - 'pmem_next': 'static double *', - # Modules/_weakref.c - 'weakref_functions': 'static PyMethodDef ', -} -INLINE = { - # Modules/_tracemalloc.c - 'allocators': 'static struct { PyMemAllocatorEx mem; PyMemAllocatorEx raw; PyMemAllocatorEx obj; } ', - # Modules/faulthandler.c - 'fatal_error': 'static struct { int enabled; PyObject *file; int fd; int all_threads; PyInterpreterState *interp; void *exc_handler; } ', - 'thread': 'static struct { PyObject *file; int fd; PY_TIMEOUT_T timeout_us; int repeat; PyInterpreterState *interp; int exit; char *header; size_t header_len; PyThread_type_lock cancel_event; PyThread_type_lock running; } ', - # Modules/signalmodule.c - 'Handlers': 'static volatile struct { _Py_atomic_int tripped; PyObject *func; } Handlers[NSIG]', - 'wakeup': 'static volatile struct { SOCKET_T fd; int warn_on_full_buffer; int use_send; } ', - # Python/dynload_shlib.c - 'handles': 'static struct { dev_t dev; ino_t ino; void *handle; } handles[128]', - # Objects/obmalloc.c - '_PyMem_Debug': 'static struct { debug_alloc_api_t raw; debug_alloc_api_t mem; debug_alloc_api_t obj; } ', - # Python/bootstrap_hash.c - 'urandom_cache': 'static struct { int fd; dev_t st_dev; ino_t st_ino; } ', - } -FUNC = { - # Objects/object.c - '_Py_abstract_hack': 'Py_ssize_t (*_Py_abstract_hack)(PyObject *)', - # Parser/myreadline.c - 'PyOS_InputHook': 'int (*PyOS_InputHook)(void)', - # Python/pylifecycle.c - '_PyOS_mystrnicmp_hack': 'int (*_PyOS_mystrnicmp_hack)(const char *, const char *, Py_ssize_t)', - # Parser/myreadline.c - 'PyOS_ReadlineFunctionPointer': 'char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, const char *)', - } -IMPLIED = { - # Objects/boolobject.c - '_Py_FalseStruct': 'static struct _longobject ', - '_Py_TrueStruct': 'static struct _longobject ', - # Modules/config.c - '_PyImport_Inittab': 'struct _inittab _PyImport_Inittab[]', - } -GLOBALS = {} -GLOBALS.update(MULTILINE) -GLOBALS.update(INLINE) -GLOBALS.update(FUNC) -GLOBALS.update(IMPLIED) - -LOCALS = { - 'buildinfo': ('Modules/getbuildinfo.c', - 'Py_GetBuildInfo', - 'static char buildinfo[50 + sizeof(GITVERSION) + ((sizeof(GITTAG) > sizeof(GITBRANCH)) ? sizeof(GITTAG) : sizeof(GITBRANCH))]'), - 'methods': ('Python/codecs.c', - '_PyCodecRegistry_Init', - 'static struct { char *name; PyMethodDef def; } methods[]'), - } - - -def _known(symbol): - if symbol.funcname: - if symbol.funcname != UNKNOWN or symbol.filename != UNKNOWN: - raise KeyError(symbol.name) - filename, funcname, decl = LOCALS[symbol.name] - varid = ID(filename, funcname, symbol.name) - elif not symbol.filename or symbol.filename == UNKNOWN: - raise KeyError(symbol.name) - else: - varid = symbol.id - try: - decl = GLOBALS[symbol.name] - except KeyError: - - if symbol.name.endswith('_methods'): - decl = 'static PyMethodDef ' - elif symbol.filename == 'Objects/exceptions.c' and symbol.name.startswith(('PyExc_', '_PyExc_')): - decl = 'static PyTypeObject ' - else: - raise - if symbol.name not in decl: - decl = decl + symbol.name - return Variable(varid, 'static', decl) - - -def known_row(varid, decl): - return ( - varid.filename, - varid.funcname or '-', - varid.name, - 'variable', - decl, - ) - - -def known_rows(symbols, *, - cached=True, - _get_filenames=iter_cpython_files, - _find_match=find_matching_variable, - _find_symbols=find_variables, - _as_known=known_row, - ): - filenames = list(_get_filenames()) - cache = {} - if cached: - for symbol in symbols: - try: - found = _known(symbol) - except KeyError: - found = _find_match(symbol, cache, filenames) - if found is None: - found = Variable(symbol.id, UNKNOWN, UNKNOWN) - yield _as_known(found.id, found.vartype) - else: - raise NotImplementedError # XXX incorporate KNOWN - for variable in _find_symbols(symbols, filenames, - srccache=cache, - parse_variable=_parse_global, - ): - #variable = variable._replace( - # filename=os.path.relpath(variable.filename, REPO_ROOT)) - if variable.funcname == UNKNOWN: - print(variable) - if variable.vartype== UNKNOWN: - print(variable) - yield _as_known(variable.id, variable.vartype) - - -def generate(symbols, filename=None, *, - _generate_rows=known_rows, - _write_tsv=write_tsv, - ): - if not filename: - filename = KNOWN_FILE + '.new' - - rows = _generate_rows(symbols) - _write_tsv(filename, KNOWN_HEADER, rows) - - -if __name__ == '__main__': - from c_symbols import binary - symbols = binary.iter_symbols( - binary.PYTHON, - find_local_symbol=None, - ) - generate(symbols) diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py new file mode 100644 index 00000000000..35fa296251e --- /dev/null +++ b/Tools/c-analyzer/cpython/_parser.py @@ -0,0 +1,308 @@ +import os.path +import re + +from c_common.fsutil import expand_filenames, iter_files_by_suffix +from c_parser.preprocessor import ( + get_preprocessor as _get_preprocessor, +) +from c_parser import ( + parse_file as _parse_file, + parse_files as _parse_files, +) +from . import REPO_ROOT, INCLUDE_DIRS, SOURCE_DIRS + + +GLOB_ALL = '**/*' + + +def clean_lines(text): + """Clear out comments, blank lines, and leading/trailing whitespace.""" + lines = (line.strip() for line in text.splitlines()) + lines = (line.partition('#')[0].rstrip() + for line in lines + if line and not line.startswith('#')) + glob_all = f'{GLOB_ALL} ' + lines = (re.sub(r'^[*] ', glob_all, line) for line in lines) + lines = (os.path.join(REPO_ROOT, line) for line in lines) + return list(lines) + + +''' +@begin=sh@ +./python ../c-parser/cpython.py + --exclude '+../c-parser/EXCLUDED' + --macros '+../c-parser/MACROS' + --incldirs '+../c-parser/INCL_DIRS' + --same './Include/cpython/' + Include/*.h + Include/internal/*.h + Modules/**/*.c + Objects/**/*.c + Parser/**/*.c + Python/**/*.c +@end=sh@ +''' + +GLOBS = [ + 'Include/*.h', + 'Include/internal/*.h', + 'Modules/**/*.c', + 'Objects/**/*.c', + 'Parser/**/*.c', + 'Python/**/*.c', +] + +EXCLUDED = clean_lines(''' +# @begin=conf@ + +# Rather than fixing for this one, we manually make sure it's okay. +Modules/_sha3/kcp/KeccakP-1600-opt64.c + +# OSX +#Modules/_ctypes/darwin/*.c +#Modules/_ctypes/libffi_osx/*.c +Modules/_scproxy.c # SystemConfiguration/SystemConfiguration.h + +# Windows +Modules/_winapi.c # windows.h +Modules/overlapped.c # winsock.h +Python/dynload_win.c # windows.h + +# other OS-dependent +Python/dynload_dl.c # dl.h +Python/dynload_hpux.c # dl.h +Python/dynload_aix.c # sys/ldr.h + +# @end=conf@ +''') + +# XXX Fix the parser. +EXCLUDED += clean_lines(''' +# The tool should be able to parse these... + +Modules/_dbmmodule.c +Modules/cjkcodecs/_codecs_*.c +Modules/expat/xmlrole.c +Modules/expat/xmlparse.c +Python/initconfig.c +''') + +INCL_DIRS = clean_lines(''' +# @begin=tsv@ + +glob dirname +* . +* ./Include +* ./Include/internal + +Modules/_tkinter.c /usr/include/tcl8.6 +Modules/tkappinit.c /usr/include/tcl +Modules/_decimal/**/*.c Modules/_decimal/libmpdec + +# @end=tsv@ +''')[1:] + +MACROS = clean_lines(''' +# @begin=tsv@ + +glob name value + +Include/internal/*.h Py_BUILD_CORE 1 +Python/**/*.c Py_BUILD_CORE 1 +Parser/**/*.c Py_BUILD_CORE 1 +Objects/**/*.c Py_BUILD_CORE 1 + +Modules/faulthandler.c Py_BUILD_CORE 1 +Modules/_functoolsmodule.c Py_BUILD_CORE 1 +Modules/gcmodule.c Py_BUILD_CORE 1 +Modules/getpath.c Py_BUILD_CORE 1 +Modules/_io/*.c Py_BUILD_CORE 1 +Modules/itertoolsmodule.c Py_BUILD_CORE 1 +Modules/_localemodule.c Py_BUILD_CORE 1 +Modules/main.c Py_BUILD_CORE 1 +Modules/posixmodule.c Py_BUILD_CORE 1 +Modules/signalmodule.c Py_BUILD_CORE 1 +Modules/_threadmodule.c Py_BUILD_CORE 1 +Modules/_tracemalloc.c Py_BUILD_CORE 1 +Modules/_asynciomodule.c Py_BUILD_CORE 1 +Modules/mathmodule.c Py_BUILD_CORE 1 +Modules/cmathmodule.c Py_BUILD_CORE 1 +Modules/_weakref.c Py_BUILD_CORE 1 +Modules/sha256module.c Py_BUILD_CORE 1 +Modules/sha512module.c Py_BUILD_CORE 1 +Modules/_datetimemodule.c Py_BUILD_CORE 1 +Modules/_ctypes/cfield.c Py_BUILD_CORE 1 +Modules/_heapqmodule.c Py_BUILD_CORE 1 +Modules/_posixsubprocess.c Py_BUILD_CORE 1 + +Modules/_json.c Py_BUILD_CORE_BUILTIN 1 +Modules/_pickle.c Py_BUILD_CORE_BUILTIN 1 +Modules/_testinternalcapi.c Py_BUILD_CORE_BUILTIN 1 + +Include/cpython/abstract.h Py_CPYTHON_ABSTRACTOBJECT_H 1 +Include/cpython/bytearrayobject.h Py_CPYTHON_BYTEARRAYOBJECT_H 1 +Include/cpython/bytesobject.h Py_CPYTHON_BYTESOBJECT_H 1 +Include/cpython/ceval.h Py_CPYTHON_CEVAL_H 1 +Include/cpython/code.h Py_CPYTHON_CODE_H 1 +Include/cpython/dictobject.h Py_CPYTHON_DICTOBJECT_H 1 +Include/cpython/fileobject.h Py_CPYTHON_FILEOBJECT_H 1 +Include/cpython/fileutils.h Py_CPYTHON_FILEUTILS_H 1 +Include/cpython/frameobject.h Py_CPYTHON_FRAMEOBJECT_H 1 +Include/cpython/import.h Py_CPYTHON_IMPORT_H 1 +Include/cpython/interpreteridobject.h Py_CPYTHON_INTERPRETERIDOBJECT_H 1 +Include/cpython/listobject.h Py_CPYTHON_LISTOBJECT_H 1 +Include/cpython/methodobject.h Py_CPYTHON_METHODOBJECT_H 1 +Include/cpython/object.h Py_CPYTHON_OBJECT_H 1 +Include/cpython/objimpl.h Py_CPYTHON_OBJIMPL_H 1 +Include/cpython/pyerrors.h Py_CPYTHON_ERRORS_H 1 +Include/cpython/pylifecycle.h Py_CPYTHON_PYLIFECYCLE_H 1 +Include/cpython/pymem.h Py_CPYTHON_PYMEM_H 1 +Include/cpython/pystate.h Py_CPYTHON_PYSTATE_H 1 +Include/cpython/sysmodule.h Py_CPYTHON_SYSMODULE_H 1 +Include/cpython/traceback.h Py_CPYTHON_TRACEBACK_H 1 +Include/cpython/tupleobject.h Py_CPYTHON_TUPLEOBJECT_H 1 +Include/cpython/unicodeobject.h Py_CPYTHON_UNICODEOBJECT_H 1 + +# implied include of pyport.h +Include/**/*.h PyAPI_DATA(RTYPE) extern RTYPE +Include/**/*.h PyAPI_FUNC(RTYPE) RTYPE +Include/**/*.h Py_DEPRECATED(VER) /* */ +Include/**/*.h _Py_NO_RETURN /* */ +Include/**/*.h PYLONG_BITS_IN_DIGIT 30 +Modules/**/*.c PyMODINIT_FUNC PyObject* +Objects/unicodeobject.c PyMODINIT_FUNC PyObject* +Python/marshal.c PyMODINIT_FUNC PyObject* +Python/_warnings.c PyMODINIT_FUNC PyObject* +Python/Python-ast.c PyMODINIT_FUNC PyObject* +Python/import.c PyMODINIT_FUNC PyObject* +Modules/_testcapimodule.c PyAPI_FUNC(RTYPE) RTYPE +Python/getargs.c PyAPI_FUNC(RTYPE) RTYPE + +# implied include of exports.h +#Modules/_io/bytesio.c Py_EXPORTED_SYMBOL /* */ + +# implied include of object.h +Include/**/*.h PyObject_HEAD PyObject ob_base; +Include/**/*.h PyObject_VAR_HEAD PyVarObject ob_base; + +# implied include of pyconfig.h +Include/**/*.h SIZEOF_WCHAR_T 4 + +# implied include of <unistd.h> +Include/**/*.h _POSIX_THREADS 1 + +# from Makefile +Modules/getpath.c PYTHONPATH 1 +Modules/getpath.c PREFIX ... +Modules/getpath.c EXEC_PREFIX ... +Modules/getpath.c VERSION ... +Modules/getpath.c VPATH ... + +# from Modules/_sha3/sha3module.c +Modules/_sha3/kcp/KeccakP-1600-inplace32BI.c PLATFORM_BYTE_ORDER 4321 # force big-endian +Modules/_sha3/kcp/*.c KeccakOpt 64 +Modules/_sha3/kcp/*.c KeccakP200_excluded 1 +Modules/_sha3/kcp/*.c KeccakP400_excluded 1 +Modules/_sha3/kcp/*.c KeccakP800_excluded 1 + +# See: setup.py +Modules/_decimal/**/*.c CONFIG_64 1 +Modules/_decimal/**/*.c ASM 1 +Modules/expat/xmlparse.c HAVE_EXPAT_CONFIG_H 1 +Modules/expat/xmlparse.c XML_POOR_ENTROPY 1 +Modules/_dbmmodule.c HAVE_GDBM_DASH_NDBM_H 1 + +# @end=tsv@ +''')[1:] + +# -pthread +# -Wno-unused-result +# -Wsign-compare +# -g +# -Og +# -Wall +# -std=c99 +# -Wextra +# -Wno-unused-result -Wno-unused-parameter +# -Wno-missing-field-initializers +# -Werror=implicit-function-declaration + +SAME = [ + './Include/cpython/', +] + + +def resolve_filename(filename): + orig = filename + filename = os.path.normcase(os.path.normpath(filename)) + if os.path.isabs(filename): + if os.path.relpath(filename, REPO_ROOT).startswith('.'): + raise Exception(f'{orig!r} is outside the repo ({REPO_ROOT})') + return filename + else: + return os.path.join(REPO_ROOT, filename) + + +def iter_filenames(*, search=False): + if search: + yield from iter_files_by_suffix(INCLUDE_DIRS, ('.h',)) + yield from iter_files_by_suffix(SOURCE_DIRS, ('.c',)) + else: + globs = (os.path.join(REPO_ROOT, file) for file in GLOBS) + yield from expand_filenames(globs) + + +def get_preprocessor(*, + file_macros=None, + file_incldirs=None, + file_same=None, + **kwargs + ): + macros = tuple(MACROS) + if file_macros: + macros += tuple(file_macros) + incldirs = tuple(INCL_DIRS) + if file_incldirs: + incldirs += tuple(file_incldirs) + return _get_preprocessor( + file_macros=macros, + file_incldirs=incldirs, + file_same=file_same, + **kwargs + ) + + +def parse_file(filename, *, + match_kind=None, + ignore_exc=None, + log_err=None, + ): + get_file_preprocessor = get_preprocessor( + ignore_exc=ignore_exc, + log_err=log_err, + ) + yield from _parse_file( + filename, + match_kind=match_kind, + get_file_preprocessor=get_file_preprocessor, + ) + + +def parse_files(filenames=None, *, + match_kind=None, + ignore_exc=None, + log_err=None, + get_file_preprocessor=None, + **file_kwargs + ): + if get_file_preprocessor is None: + get_file_preprocessor = get_preprocessor( + ignore_exc=ignore_exc, + log_err=log_err, + ) + yield from _parse_files( + filenames, + match_kind=match_kind, + get_file_preprocessor=get_file_preprocessor, + **file_kwargs + ) diff --git a/Tools/c-analyzer/cpython/files.py b/Tools/c-analyzer/cpython/files.py deleted file mode 100644 index 543097af7bc..00000000000 --- a/Tools/c-analyzer/cpython/files.py +++ /dev/null @@ -1,29 +0,0 @@ -from c_analyzer.common.files import ( - C_SOURCE_SUFFIXES, walk_tree, iter_files_by_suffix, - ) - -from . import SOURCE_DIRS, REPO_ROOT - -# XXX need tests: -# * iter_files() - - -def iter_files(*, - walk=walk_tree, - _files=iter_files_by_suffix, - ): - """Yield each file in the tree for each of the given directory names.""" - excludedtrees = [ - os.path.join('Include', 'cpython', ''), - ] - def is_excluded(filename): - for root in excludedtrees: - if filename.startswith(root): - return True - return False - for filename in _files(SOURCE_DIRS, C_SOURCE_SUFFIXES, REPO_ROOT, - walk=walk, - ): - if is_excluded(filename): - continue - yield filename diff --git a/Tools/c-analyzer/cpython/find.py b/Tools/c-analyzer/cpython/find.py deleted file mode 100644 index a7bc0b477b8..00000000000 --- a/Tools/c-analyzer/cpython/find.py +++ /dev/null @@ -1,101 +0,0 @@ -import os.path - -from c_analyzer.common import files -from c_analyzer.common.info import UNKNOWN, ID -from c_analyzer.variables import find as _common - -from . import SOURCE_DIRS, PYTHON, REPO_ROOT -from .known import ( - from_file as known_from_file, - DATA_FILE as KNOWN_FILE, - ) -from .supported import ( - ignored_from_file, IGNORED_FILE, is_supported, _is_object, - ) - -# XXX need tests: -# * vars_from_binary() -# * vars_from_source() -# * supported_vars() - - -def _handle_id(filename, funcname, name, *, - _relpath=os.path.relpath, - ): - filename = _relpath(filename, REPO_ROOT) - return ID(filename, funcname, name) - - -def vars_from_binary(*, - known=KNOWN_FILE, - _known_from_file=known_from_file, - _iter_files=files.iter_files_by_suffix, - _iter_vars=_common.vars_from_binary, - ): - """Yield a Variable for each found Symbol. - - Details are filled in from the given "known" variables and types. - """ - if isinstance(known, str): - known = _known_from_file(known) - dirnames = SOURCE_DIRS - suffixes = ('.c',) - filenames = _iter_files(dirnames, suffixes) - # XXX For now we only use known variables (no source lookup). - filenames = None - yield from _iter_vars(PYTHON, - known=known, - filenames=filenames, - handle_id=_handle_id, - check_filename=(lambda n: True), - ) - - -def vars_from_source(*, - preprocessed=None, - known=KNOWN_FILE, - _known_from_file=known_from_file, - _iter_files=files.iter_files_by_suffix, - _iter_vars=_common.vars_from_source, - ): - """Yield a Variable for each declaration in the raw source code. - - Details are filled in from the given "known" variables and types. - """ - if isinstance(known, str): - known = _known_from_file(known) - dirnames = SOURCE_DIRS - suffixes = ('.c',) - filenames = _iter_files(dirnames, suffixes) - yield from _iter_vars(filenames, - preprocessed=preprocessed, - known=known, - handle_id=_handle_id, - ) - - -def supported_vars(*, - known=KNOWN_FILE, - ignored=IGNORED_FILE, - skip_objects=False, - _known_from_file=known_from_file, - _ignored_from_file=ignored_from_file, - _iter_vars=vars_from_binary, - _is_supported=is_supported, - ): - """Yield (var, is supported) for each found variable.""" - if isinstance(known, str): - known = _known_from_file(known) - if isinstance(ignored, str): - ignored = _ignored_from_file(ignored) - - for var in _iter_vars(known=known): - if not var.isglobal: - continue - elif var.vartype == UNKNOWN: - yield var, None - # XXX Support proper filters instead. - elif skip_objects and _is_object(found.vartype): - continue - else: - yield var, _is_supported(var, ignored, known) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv new file mode 100644 index 00000000000..2c456db063e --- /dev/null +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -0,0 +1,2 @@ +filename funcname name reason +#??? - somevar ??? diff --git a/Tools/c-analyzer/cpython/known.py b/Tools/c-analyzer/cpython/known.py deleted file mode 100644 index c3cc2c06026..00000000000 --- a/Tools/c-analyzer/cpython/known.py +++ /dev/null @@ -1,66 +0,0 @@ -import csv -import os.path - -from c_analyzer.parser.declarations import extract_storage -from c_analyzer.variables import known as _common -from c_analyzer.variables.info import Variable - -from . import DATA_DIR - - -# XXX need tests: -# * from_file() -# * look_up_variable() - - -DATA_FILE = os.path.join(DATA_DIR, 'known.tsv') - - -def _get_storage(decl, infunc): - # statics - if decl.startswith(('Py_LOCAL(', 'Py_LOCAL_INLINE(')): - return 'static' - if decl.startswith(('_Py_IDENTIFIER(', '_Py_static_string(')): - return 'static' - if decl.startswith('PyDoc_VAR('): - return 'static' - if decl.startswith(('SLOT1BINFULL(', 'SLOT1BIN(')): - return 'static' - if decl.startswith('WRAP_METHOD('): - return 'static' - # public extern - if decl.startswith('PyAPI_DATA('): - return 'extern' - # Fall back to the normal handler. - return extract_storage(decl, infunc=infunc) - - -def _handle_var(varid, decl): -# if varid.name == 'id' and decl == UNKNOWN: -# # None of these are variables. -# decl = 'int id'; - storage = _get_storage(decl, varid.funcname) - return Variable(varid, storage, decl) - - -def from_file(infile=DATA_FILE, *, - _from_file=_common.from_file, - _handle_var=_handle_var, - ): - """Return the info for known declarations in the given file.""" - return _from_file(infile, handle_var=_handle_var) - - -def look_up_variable(varid, knownvars, *, - _lookup=_common.look_up_variable, - ): - """Return the known variable matching the given ID. - - "knownvars" is a mapping of ID to Variable. - - "match_files" is used to verify if two filenames point to - the same file. - - If no match is found then None is returned. - """ - return _lookup(varid, knownvars) diff --git a/Tools/c-analyzer/cpython/known.tsv b/Tools/c-analyzer/cpython/known.tsv new file mode 100644 index 00000000000..a48ef02dc6f --- /dev/null +++ b/Tools/c-analyzer/cpython/known.tsv @@ -0,0 +1,3 @@ +filename funcname name kind declaration +#filename funcname name kind is_supported declaration +#??? - PyWideStringList typedef ??? diff --git a/Tools/c-analyzer/cpython/supported.py b/Tools/c-analyzer/cpython/supported.py deleted file mode 100644 index 18786eefd8d..00000000000 --- a/Tools/c-analyzer/cpython/supported.py +++ /dev/null @@ -1,398 +0,0 @@ -import os.path -import re - -from c_analyzer.common.info import ID -from c_analyzer.common.util import read_tsv, write_tsv - -from . import DATA_DIR - -# XXX need tests: -# * generate / script - - -IGNORED_FILE = os.path.join(DATA_DIR, 'ignored.tsv') - -IGNORED_COLUMNS = ('filename', 'funcname', 'name', 'kind', 'reason') -IGNORED_HEADER = '\t'.join(IGNORED_COLUMNS) - -# XXX Move these to ignored.tsv. -IGNORED = { - # global - 'PyImport_FrozenModules': 'process-global', - 'M___hello__': 'process-global', - 'inittab_copy': 'process-global', - 'PyHash_Func': 'process-global', - '_Py_HashSecret_Initialized': 'process-global', - '_TARGET_LOCALES': 'process-global', - - # startup (only changed before/during) - '_PyRuntime': 'runtime startup', - 'runtime_initialized': 'runtime startup', - 'static_arg_parsers': 'runtime startup', - 'orig_argv': 'runtime startup', - 'opt_ptr': 'runtime startup', - '_preinit_warnoptions': 'runtime startup', - '_Py_StandardStreamEncoding': 'runtime startup', - 'Py_FileSystemDefaultEncoding': 'runtime startup', - '_Py_StandardStreamErrors': 'runtime startup', - 'Py_FileSystemDefaultEncodeErrors': 'runtime startup', - 'Py_BytesWarningFlag': 'runtime startup', - 'Py_DebugFlag': 'runtime startup', - 'Py_DontWriteBytecodeFlag': 'runtime startup', - 'Py_FrozenFlag': 'runtime startup', - 'Py_HashRandomizationFlag': 'runtime startup', - 'Py_IgnoreEnvironmentFlag': 'runtime startup', - 'Py_InspectFlag': 'runtime startup', - 'Py_InteractiveFlag': 'runtime startup', - 'Py_IsolatedFlag': 'runtime startup', - 'Py_NoSiteFlag': 'runtime startup', - 'Py_NoUserSiteDirectory': 'runtime startup', - 'Py_OptimizeFlag': 'runtime startup', - 'Py_QuietFlag': 'runtime startup', - 'Py_UTF8Mode': 'runtime startup', - 'Py_UnbufferedStdioFlag': 'runtime startup', - 'Py_VerboseFlag': 'runtime startup', - '_Py_path_config': 'runtime startup', - '_PyOS_optarg': 'runtime startup', - '_PyOS_opterr': 'runtime startup', - '_PyOS_optind': 'runtime startup', - '_Py_HashSecret': 'runtime startup', - - # REPL - '_PyOS_ReadlineLock': 'repl', - '_PyOS_ReadlineTState': 'repl', - - # effectively const - 'tracemalloc_empty_traceback': 'const', - '_empty_bitmap_node': 'const', - 'posix_constants_pathconf': 'const', - 'posix_constants_confstr': 'const', - 'posix_constants_sysconf': 'const', - '_PySys_ImplCacheTag': 'const', - '_PySys_ImplName': 'const', - 'PyImport_Inittab': 'const', - '_PyImport_DynLoadFiletab': 'const', - '_PyParser_Grammar': 'const', - 'Py_hexdigits': 'const', - '_PyImport_Inittab': 'const', - '_PyByteArray_empty_string': 'const', - '_PyLong_DigitValue': 'const', - '_Py_SwappedOp': 'const', - 'PyStructSequence_UnnamedField': 'const', - - # signals are main-thread only - 'faulthandler_handlers': 'signals are main-thread only', - 'user_signals': 'signals are main-thread only', - 'wakeup': 'signals are main-thread only', - - # hacks - '_PySet_Dummy': 'only used as a placeholder', - } - -BENIGN = 'races here are benign and unlikely' - - -def is_supported(variable, ignored=None, known=None, *, - _ignored=(lambda *a, **k: _is_ignored(*a, **k)), - _vartype_okay=(lambda *a, **k: _is_vartype_okay(*a, **k)), - ): - """Return True if the given global variable is okay in CPython.""" - if _ignored(variable, - ignored and ignored.get('variables')): - return True - elif _vartype_okay(variable.vartype, - ignored.get('types')): - return True - else: - return False - - -def _is_ignored(variable, ignoredvars=None, *, - _IGNORED=IGNORED, - ): - """Return the reason if the variable is a supported global. - - Return None if the variable is not a supported global. - """ - if ignoredvars and (reason := ignoredvars.get(variable.id)): - return reason - - if variable.funcname is None: - if reason := _IGNORED.get(variable.name): - return reason - - # compiler - if variable.filename == 'Python/graminit.c': - if variable.vartype.startswith('static state '): - return 'compiler' - if variable.filename == 'Python/symtable.c': - if variable.vartype.startswith('static identifier '): - return 'compiler' - if variable.filename == 'Python/Python-ast.c': - # These should be const. - if variable.name.endswith('_field'): - return 'compiler' - if variable.name.endswith('_attribute'): - return 'compiler' - - # other - if variable.filename == 'Python/dtoa.c': - # guarded by lock? - if variable.name in ('p5s', 'freelist'): - return 'dtoa is thread-safe?' - if variable.name in ('private_mem', 'pmem_next'): - return 'dtoa is thread-safe?' - if variable.filename == 'Python/thread.c': - # Threads do not become an issue until after these have been set - # and these never get changed after that. - if variable.name in ('initialized', 'thread_debug'): - return 'thread-safe' - if variable.filename == 'Python/getversion.c': - if variable.name == 'version': - # Races are benign here, as well as unlikely. - return BENIGN - if variable.filename == 'Python/fileutils.c': - if variable.name == 'force_ascii': - return BENIGN - if variable.name == 'ioctl_works': - return BENIGN - if variable.name == '_Py_open_cloexec_works': - return BENIGN - if variable.filename == 'Python/codecs.c': - if variable.name == 'ucnhash_CAPI': - return BENIGN - if variable.filename == 'Python/bootstrap_hash.c': - if variable.name == 'getrandom_works': - return BENIGN - if variable.filename == 'Objects/unicodeobject.c': - if variable.name == 'ucnhash_CAPI': - return BENIGN - if variable.name == 'bloom_linebreak': - # *mostly* benign - return BENIGN - if variable.filename == 'Modules/getbuildinfo.c': - if variable.name == 'buildinfo': - # The static is used for pre-allocation. - return BENIGN - if variable.filename == 'Modules/posixmodule.c': - if variable.name == 'ticks_per_second': - return BENIGN - if variable.name == 'dup3_works': - return BENIGN - if variable.filename == 'Modules/timemodule.c': - if variable.name == 'ticks_per_second': - return BENIGN - if variable.filename == 'Objects/longobject.c': - if variable.name == 'log_base_BASE': - return BENIGN - if variable.name == 'convwidth_base': - return BENIGN - if variable.name == 'convmultmax_base': - return BENIGN - - return None - - -def _is_vartype_okay(vartype, ignoredtypes=None): - if _is_object(vartype): - return None - - if vartype.startswith('static const '): - return 'const' - if vartype.startswith('const '): - return 'const' - - # components for TypeObject definitions - for name in ('PyMethodDef', 'PyGetSetDef', 'PyMemberDef'): - if name in vartype: - return 'const' - for name in ('PyNumberMethods', 'PySequenceMethods', 'PyMappingMethods', - 'PyBufferProcs', 'PyAsyncMethods'): - if name in vartype: - return 'const' - for name in ('slotdef', 'newfunc'): - if name in vartype: - return 'const' - - # structseq - for name in ('PyStructSequence_Desc', 'PyStructSequence_Field'): - if name in vartype: - return 'const' - - # other definiitions - if 'PyModuleDef' in vartype: - return 'const' - - # thread-safe - if '_Py_atomic_int' in vartype: - return 'thread-safe' - if 'pthread_condattr_t' in vartype: - return 'thread-safe' - - # startup - if '_Py_PreInitEntry' in vartype: - return 'startup' - - # global -# if 'PyMemAllocatorEx' in vartype: -# return True - - # others -# if 'PyThread_type_lock' in vartype: -# return True - - # XXX ??? - # _Py_tss_t - # _Py_hashtable_t - # stack_t - # _PyUnicode_Name_CAPI - - # functions - if '(' in vartype and '[' not in vartype: - return 'function pointer' - - # XXX finish! - # * allow const values? - #raise NotImplementedError - return None - - -PYOBJECT_RE = re.compile(r''' - ^ - ( - # must start with "static " - static \s+ - ( - identifier - ) - \b - ) | - ( - # may start with "static " - ( static \s+ )? - ( - .* - ( - PyObject | - PyTypeObject | - _? Py \w+ Object | - _PyArg_Parser | - _Py_Identifier | - traceback_t | - PyAsyncGenASend | - _PyAsyncGenWrappedValue | - PyContext | - method_cache_entry - ) - \b - ) | - ( - ( - _Py_IDENTIFIER | - _Py_static_string - ) - [(] - ) - ) - ''', re.VERBOSE) - - -def _is_object(vartype): - if 'PyDictKeysObject' in vartype: - return False - if PYOBJECT_RE.match(vartype): - return True - if vartype.endswith((' _Py_FalseStruct', ' _Py_TrueStruct')): - return True - - # XXX Add more? - - #for part in vartype.split(): - # # XXX const is automatic True? - # if part == 'PyObject' or part.startswith('PyObject['): - # return True - return False - - -def ignored_from_file(infile, *, - _read_tsv=read_tsv, - ): - """Yield a Variable for each ignored var in the file.""" - ignored = { - 'variables': {}, - #'types': {}, - #'constants': {}, - #'macros': {}, - } - for row in _read_tsv(infile, IGNORED_HEADER): - filename, funcname, name, kind, reason = row - if not funcname or funcname == '-': - funcname = None - id = ID(filename, funcname, name) - if kind == 'variable': - values = ignored['variables'] - else: - raise ValueError(f'unsupported kind in row {row}') - values[id] = reason - return ignored - - -################################## -# generate - -def _get_row(varid, reason): - return ( - varid.filename, - varid.funcname or '-', - varid.name, - 'variable', - str(reason), - ) - - -def _get_rows(variables, ignored=None, *, - _as_row=_get_row, - _is_ignored=_is_ignored, - _vartype_okay=_is_vartype_okay, - ): - count = 0 - for variable in variables: - reason = _is_ignored(variable, - ignored and ignored.get('variables'), - ) - if not reason: - reason = _vartype_okay(variable.vartype, - ignored and ignored.get('types')) - if not reason: - continue - - print(' ', variable, repr(reason)) - yield _as_row(variable.id, reason) - count += 1 - print(f'total: {count}') - - -def _generate_ignored_file(variables, filename=None, *, - _generate_rows=_get_rows, - _write_tsv=write_tsv, - ): - if not filename: - filename = IGNORED_FILE + '.new' - rows = _generate_rows(variables) - _write_tsv(filename, IGNORED_HEADER, rows) - - -if __name__ == '__main__': - from cpython import SOURCE_DIRS - from cpython.known import ( - from_file as known_from_file, - DATA_FILE as KNOWN_FILE, - ) - # XXX This is wrong! - from . import find - known = known_from_file(KNOWN_FILE) - knownvars = (known or {}).get('variables') - variables = find.globals_from_binary(knownvars=knownvars, - dirnames=SOURCE_DIRS) - - _generate_ignored_file(variables) diff --git a/Tools/c-analyzer/ignored-globals.txt b/Tools/c-analyzer/ignored-globals.txt deleted file mode 100644 index ce6d1d80514..00000000000 --- a/Tools/c-analyzer/ignored-globals.txt +++ /dev/null @@ -1,492 +0,0 @@ -# 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 -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_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 diff --git a/Tools/c-analyzer/ignored.tsv b/Tools/c-analyzer/ignored.tsv deleted file mode 100644 index a0e0e503da6..00000000000 --- a/Tools/c-analyzer/ignored.tsv +++ /dev/null @@ -1 +0,0 @@ -filename funcname name kind reason diff --git a/Tools/c-analyzer/known.tsv b/Tools/c-analyzer/known.tsv deleted file mode 100644 index f8c12a3944d..00000000000 --- a/Tools/c-analyzer/known.tsv +++ /dev/null @@ -1,1927 +0,0 @@ -filename funcname name kind declaration -Modules/_abc.c - _abc_data_type variable static PyTypeObject _abc_data_type -Modules/_abc.c - abc_invalidation_counter variable static unsigned long long abc_invalidation_counter -Modules/_abc.c - _abcmodule variable static struct PyModuleDef _abcmodule -Python/import.c import_find_and_load accumulated variable static _PyTime_t accumulated -Modules/itertoolsmodule.c - accumulate_methods variable static PyMethodDef accumulate_methods -Modules/itertoolsmodule.c - accumulate_type variable static PyTypeObject accumulate_type -Python/Python-ast.c - Add_singleton variable static PyObject *Add_singleton -Python/Python-ast.c - Add_type variable static PyTypeObject *Add_type -Objects/genobject.c - ag_asend_freelist variable static PyAsyncGenASend *ag_asend_freelist[_PyAsyncGen_MAXFREELIST] -Objects/genobject.c - ag_asend_freelist_free variable static int ag_asend_freelist_free -Objects/genobject.c - ag_value_freelist variable static _PyAsyncGenWrappedValue *ag_value_freelist[_PyAsyncGen_MAXFREELIST] -Objects/genobject.c - ag_value_freelist_free variable static int ag_value_freelist_free -Python/Python-ast.c - alias_fields variable static const char *alias_fields[] -Python/Python-ast.c - alias_type variable static PyTypeObject *alias_type -Modules/_tracemalloc.c - allocators variable static struct { PyMemAllocatorEx mem; PyMemAllocatorEx raw; PyMemAllocatorEx obj; } allocators -Python/Python-ast.c - And_singleton variable static PyObject *And_singleton -Python/Python-ast.c - And_type variable static PyTypeObject *And_type -Python/Python-ast.c - AnnAssign_fields variable static const char *AnnAssign_fields[] -Python/Python-ast.c - AnnAssign_type variable static PyTypeObject *AnnAssign_type -Python/compile.c - __annotations__ variable static PyObject *__annotations__ -Objects/obmalloc.c - arenas variable static struct arena_object* arenas -Python/Python-ast.c - arg_attributes variable static const char *arg_attributes[] -Python/Python-ast.c - arg_fields variable static const char *arg_fields[] -Python/Python-ast.c - arg_type variable static PyTypeObject *arg_type -Python/Python-ast.c - arguments_fields variable static const char *arguments_fields[] -Python/Python-ast.c - arguments_type variable static PyTypeObject *arguments_type -Python/Python-ast.c - Assert_fields variable static const char *Assert_fields[] -Python/compile.c compiler_assert assertion_error variable static PyObject *assertion_error -Python/Python-ast.c - Assert_type variable static PyTypeObject *Assert_type -Python/Python-ast.c - Assign_fields variable static const char *Assign_fields[] -Python/Python-ast.c - Assign_type variable static PyTypeObject *Assign_type -Python/Python-ast.c - _astmodule variable static struct PyModuleDef _astmodule -Python/Python-ast.c - AST_type variable static PyTypeObject AST_type -Python/Python-ast.c - ast_type_getsets variable static PyGetSetDef ast_type_getsets[] -Python/Python-ast.c - ast_type_methods variable static PyMethodDef ast_type_methods -Python/Python-ast.c - AsyncFor_fields variable static const char *AsyncFor_fields[] -Python/Python-ast.c - AsyncFor_type variable static PyTypeObject *AsyncFor_type -Python/Python-ast.c - AsyncFunctionDef_fields variable static const char *AsyncFunctionDef_fields[] -Python/Python-ast.c - AsyncFunctionDef_type variable static PyTypeObject *AsyncFunctionDef_type -Objects/genobject.c - async_gen_as_async variable static PyAsyncMethods async_gen_as_async -Objects/genobject.c - async_gen_asend_as_async variable static PyAsyncMethods async_gen_asend_as_async -Objects/genobject.c - async_gen_asend_methods variable static PyMethodDef async_gen_asend_methods -Objects/genobject.c - async_gen_athrow_as_async variable static PyAsyncMethods async_gen_athrow_as_async -Objects/genobject.c - async_gen_athrow_methods variable static PyMethodDef async_gen_athrow_methods -Objects/genobject.c - async_gen_getsetlist variable static PyGetSetDef async_gen_getsetlist[] -Python/sysmodule.c - asyncgen_hooks_desc variable static PyStructSequence_Desc asyncgen_hooks_desc -Python/sysmodule.c - asyncgen_hooks_fields variable static PyStructSequence_Field asyncgen_hooks_fields[] -Python/sysmodule.c - AsyncGenHooksType variable static PyTypeObject AsyncGenHooksType -Objects/genobject.c - async_gen_memberlist variable static PyMemberDef async_gen_memberlist[] -Objects/genobject.c - async_gen_methods variable static PyMethodDef async_gen_methods -Python/Python-ast.c - AsyncWith_fields variable static const char *AsyncWith_fields[] -Python/Python-ast.c - AsyncWith_type variable static PyTypeObject *AsyncWith_type -Modules/atexitmodule.c - atexit_methods variable static PyMethodDef atexit_methods -Modules/atexitmodule.c - atexitmodule variable static struct PyModuleDef atexitmodule -Modules/atexitmodule.c - atexit_slots variable static PyModuleDef_Slot atexit_slots[] -Modules/_operator.c - attrgetter_methods variable static PyMethodDef attrgetter_methods -Modules/_operator.c - attrgetter_type variable static PyTypeObject attrgetter_type -Python/Python-ast.c - Attribute_fields variable static const char *Attribute_fields[] -Python/Python-ast.c - Attribute_type variable static PyTypeObject *Attribute_type -Python/Python-ast.c - AugAssign_fields variable static const char *AugAssign_fields[] -Python/Python-ast.c - AugAssign_type variable static PyTypeObject *AugAssign_type -Python/Python-ast.c - AugLoad_singleton variable static PyObject *AugLoad_singleton -Python/Python-ast.c - AugLoad_type variable static PyTypeObject *AugLoad_type -Python/Python-ast.c - AugStore_singleton variable static PyObject *AugStore_singleton -Python/Python-ast.c - AugStore_type variable static PyTypeObject *AugStore_type -Python/Python-ast.c - Await_fields variable static const char *Await_fields[] -Python/Python-ast.c - Await_type variable static PyTypeObject *Await_type -Objects/exceptions.c - BaseException_getset variable static PyGetSetDef BaseException_getset[] -Objects/exceptions.c - BaseException_members variable static struct PyMemberDef BaseException_members[] -Objects/exceptions.c - BaseException_methods variable static PyMethodDef BaseException_methods -Modules/posixmodule.c - billion variable static PyObject *billion -Python/Python-ast.c - BinOp_fields variable static const char *BinOp_fields[] -Python/Python-ast.c - BinOp_type variable static PyTypeObject *BinOp_type -Python/Python-ast.c - BitAnd_singleton variable static PyObject *BitAnd_singleton -Python/Python-ast.c - BitAnd_type variable static PyTypeObject *BitAnd_type -Python/Python-ast.c - BitOr_singleton variable static PyObject *BitOr_singleton -Python/Python-ast.c - BitOr_type variable static PyTypeObject *BitOr_type -Python/Python-ast.c - BitXor_singleton variable static PyObject *BitXor_singleton -Python/Python-ast.c - BitXor_type variable static PyTypeObject *BitXor_type -Objects/unicodeobject.c - bloom_linebreak variable static BLOOM_MASK bloom_linebreak -Objects/boolobject.c - bool_as_number variable static PyNumberMethods bool_as_number -Python/Python-ast.c - BoolOp_fields variable static const char *BoolOp_fields[] -Python/Python-ast.c - boolop_type variable static PyTypeObject *boolop_type -Python/Python-ast.c - BoolOp_type variable static PyTypeObject *BoolOp_type -Python/_warnings.c is_internal_frame bootstrap_string variable static PyObject *bootstrap_string -Python/Python-ast.c - Break_type variable static PyTypeObject *Break_type -Modules/_io/bufferedio.c - bufferediobase_methods variable static PyMethodDef bufferediobase_methods -Modules/_io/bufferedio.c - bufferedrandom_getset variable static PyGetSetDef bufferedrandom_getset[] -Modules/_io/bufferedio.c - bufferedrandom_members variable static PyMemberDef bufferedrandom_members[] -Modules/_io/bufferedio.c - bufferedrandom_methods variable static PyMethodDef bufferedrandom_methods -Modules/_io/bufferedio.c - bufferedreader_getset variable static PyGetSetDef bufferedreader_getset[] -Modules/_io/bufferedio.c - bufferedreader_members variable static PyMemberDef bufferedreader_members[] -Modules/_io/bufferedio.c - bufferedreader_methods variable static PyMethodDef bufferedreader_methods -Modules/_io/bufferedio.c - bufferedrwpair_getset variable static PyGetSetDef bufferedrwpair_getset[] -Modules/_io/bufferedio.c - bufferedrwpair_methods variable static PyMethodDef bufferedrwpair_methods -Modules/_io/bufferedio.c - bufferedwriter_getset variable static PyGetSetDef bufferedwriter_getset[] -Modules/_io/bufferedio.c - bufferedwriter_members variable static PyMemberDef bufferedwriter_members[] -Modules/_io/bufferedio.c - bufferedwriter_methods variable static PyMethodDef bufferedwriter_methods -Modules/getbuildinfo.c Py_GetBuildInfo buildinfo variable static char buildinfo[50 + sizeof(GITVERSION) + ((sizeof(GITTAG) > sizeof(GITBRANCH)) ? sizeof(GITTAG) : sizeof(GITBRANCH))] -Python/bltinmodule.c - builtin_methods variable static PyMethodDef builtin_methods -Python/bltinmodule.c - builtinsmodule variable static struct PyModuleDef builtinsmodule -Python/import.c PyImport_Import builtins_str variable static PyObject *builtins_str -Python/ceval.c make_pending_calls busy variable static int busy -Objects/bytearrayobject.c - bytearray_as_buffer variable static PyBufferProcs bytearray_as_buffer -Objects/bytearrayobject.c - bytearray_as_mapping variable static PyMappingMethods bytearray_as_mapping -Objects/bytearrayobject.c - bytearray_as_number variable static PyNumberMethods bytearray_as_number -Objects/bytearrayobject.c - bytearray_as_sequence variable static PySequenceMethods bytearray_as_sequence -Objects/bytearrayobject.c - bytearrayiter_methods variable static PyMethodDef bytearrayiter_methods -Objects/bytearrayobject.c - bytearray_methods variable static PyMethodDef bytearray_methods -Objects/bytesobject.c - bytes_as_buffer variable static PyBufferProcs bytes_as_buffer -Objects/bytesobject.c - bytes_as_mapping variable static PyMappingMethods bytes_as_mapping -Objects/bytesobject.c - bytes_as_number variable static PyNumberMethods bytes_as_number -Objects/bytesobject.c - bytes_as_sequence variable static PySequenceMethods bytes_as_sequence -Modules/_io/bytesio.c - bytesiobuf_as_buffer variable static PyBufferProcs bytesiobuf_as_buffer -Modules/_io/bytesio.c - bytesio_getsetlist variable static PyGetSetDef bytesio_getsetlist[] -Modules/_io/bytesio.c - bytesio_methods variable static PyMethodDef bytesio_methods -Objects/bytesobject.c - bytes_methods variable static PyMethodDef bytes_methods -Python/thread_pthread.h init_condattr ca variable static pthread_condattr_t ca -Python/Python-ast.c - Call_fields variable static const char *Call_fields[] -Objects/iterobject.c - calliter_methods variable static PyMethodDef calliter_methods -Python/Python-ast.c - Call_type variable static PyTypeObject *Call_type -Objects/cellobject.c - cell_getsetlist variable static PyGetSetDef cell_getsetlist[] -Modules/itertoolsmodule.c - chain_methods variable static PyMethodDef chain_methods -Modules/itertoolsmodule.c - chain_type variable static PyTypeObject chain_type -Objects/bytesobject.c - characters variable static PyBytesObject *characters[UCHAR_MAX + 1] -Python/symtable.c - __class__ variable static identifier __class__ -Python/Python-ast.c - ClassDef_fields variable static const char *ClassDef_fields[] -Python/Python-ast.c - ClassDef_type variable static PyTypeObject *ClassDef_type -Objects/funcobject.c - cm_getsetlist variable static PyGetSetDef cm_getsetlist[] -Objects/funcobject.c - cm_memberlist variable static PyMemberDef cm_memberlist[] -Python/Python-ast.c - cmpop_type variable static PyTypeObject *cmpop_type -Modules/_codecsmodule.c - _codecs_functions variable static PyMethodDef _codecs_functions[] -Modules/_codecsmodule.c - codecsmodule variable static struct PyModuleDef codecsmodule -Objects/codeobject.c - code_memberlist variable static PyMemberDef code_memberlist[] -Objects/codeobject.c - code_methods variable static PyMethodDef code_methods -Modules/_collectionsmodule.c - _collectionsmodule variable static struct PyModuleDef _collectionsmodule -Modules/itertoolsmodule.c - combinations_methods variable static PyMethodDef combinations_methods -Modules/itertoolsmodule.c - combinations_type variable static PyTypeObject combinations_type -Objects/typeobject.c object_new comma_id variable _Py_static_string(comma_id, "", "") -Python/Python-ast.c - Compare_fields variable static const char *Compare_fields[] -Python/Python-ast.c - Compare_type variable static PyTypeObject *Compare_type -Objects/complexobject.c - complex_as_number variable static PyNumberMethods complex_as_number -Objects/complexobject.c - complex_members variable static PyMemberDef complex_members[] -Objects/complexobject.c - complex_methods variable static PyMethodDef complex_methods -Python/Python-ast.c - comprehension_fields variable static const char *comprehension_fields[] -Python/Python-ast.c - comprehension_type variable static PyTypeObject *comprehension_type -Modules/itertoolsmodule.c - compress_methods variable static PyMethodDef compress_methods -Modules/itertoolsmodule.c - compress_type variable static PyTypeObject compress_type -Python/thread_pthread.h - condattr_monotonic variable static pthread_condattr_t *condattr_monotonic -Python/Python-ast.c - Constant_fields variable static const char *Constant_fields[] -Python/Python-ast.c - Constant_type variable static PyTypeObject *Constant_type -Python/Python-ast.c - Continue_type variable static PyTypeObject *Continue_type -Objects/longobject.c PyLong_FromString convmultmax_base variable static twodigits convmultmax_base[37] -Objects/longobject.c PyLong_FromString convwidth_base variable static int convwidth_base[37] -Objects/genobject.c - coro_as_async variable static PyAsyncMethods coro_as_async -Objects/genobject.c - coro_getsetlist variable static PyGetSetDef coro_getsetlist[] -Objects/genobject.c - coro_memberlist variable static PyMemberDef coro_memberlist[] -Objects/genobject.c - coro_methods variable static PyMethodDef coro_methods -Objects/genobject.c - coro_wrapper_methods variable static PyMethodDef coro_wrapper_methods -Modules/itertoolsmodule.c - count_methods variable static PyMethodDef count_methods -Modules/itertoolsmodule.c - count_type variable static PyTypeObject count_type -Python/context.c - ctx_freelist variable static PyContext *ctx_freelist -Python/context.c - ctx_freelist_len variable static int ctx_freelist_len -Modules/itertoolsmodule.c - cwr_methods variable static PyMethodDef cwr_methods -Modules/itertoolsmodule.c - cwr_type variable static PyTypeObject cwr_type -Modules/itertoolsmodule.c - cycle_methods variable static PyMethodDef cycle_methods -Modules/itertoolsmodule.c - cycle_type variable static PyTypeObject cycle_type -Objects/obmalloc.c new_arena debug_stats variable static int debug_stats -Modules/signalmodule.c - DefaultHandler variable static PyObject *DefaultHandler -Modules/_collectionsmodule.c - defdict_members variable static PyMemberDef defdict_members[] -Modules/_collectionsmodule.c - defdict_methods variable static PyMethodDef defdict_methods -Modules/_collectionsmodule.c - defdict_type variable static PyTypeObject defdict_type -Python/Python-ast.c - Delete_fields variable static const char *Delete_fields[] -Python/Python-ast.c - Delete_type variable static PyTypeObject *Delete_type -Python/Python-ast.c - Del_singleton variable static PyObject *Del_singleton -Python/Python-ast.c - Del_type variable static PyTypeObject *Del_type -Modules/_collectionsmodule.c - deque_as_number variable static PyNumberMethods deque_as_number -Modules/_collectionsmodule.c - deque_as_sequence variable static PySequenceMethods deque_as_sequence -Modules/_collectionsmodule.c - deque_getset variable static PyGetSetDef deque_getset[] -Modules/_collectionsmodule.c - dequeiter_methods variable static PyMethodDef dequeiter_methods -Modules/_collectionsmodule.c - dequeiter_type variable static PyTypeObject dequeiter_type -Modules/_collectionsmodule.c - deque_methods variable static PyMethodDef deque_methods -Modules/_collectionsmodule.c - dequereviter_type variable static PyTypeObject dequereviter_type -Modules/_collectionsmodule.c - deque_type variable static PyTypeObject deque_type -Objects/descrobject.c - descr_members variable static PyMemberDef descr_members[] -Objects/descrobject.c - descr_methods variable static PyMethodDef descr_methods -Modules/_abc.c - _destroy_def variable static PyMethodDef _destroy_def -Objects/floatobject.c - detected_double_format variable static float_format_type detected_double_format -Objects/floatobject.c - detected_float_format variable static float_format_type detected_float_format -Objects/dictobject.c - dict_as_mapping variable static PyMappingMethods dict_as_mapping -Objects/dictobject.c - dict_as_sequence variable static PySequenceMethods dict_as_sequence -Python/symtable.c - dictcomp variable static identifier dictcomp -Python/Python-ast.c - DictComp_fields variable static const char *DictComp_fields[] -Python/Python-ast.c - DictComp_type variable static PyTypeObject *DictComp_type -Python/Python-ast.c - Dict_fields variable static const char *Dict_fields[] -Objects/dictobject.c - dictitems_as_sequence variable static PySequenceMethods dictitems_as_sequence -Objects/dictobject.c - dictitems_methods variable static PyMethodDef dictitems_methods -Objects/dictobject.c - dictiter_methods variable static PyMethodDef dictiter_methods -Objects/dictobject.c - dictkeys_as_sequence variable static PySequenceMethods dictkeys_as_sequence -Objects/dictobject.c - dictkeys_methods variable static PyMethodDef dictkeys_methods -Python/Python-ast.c - Dict_type variable static PyTypeObject *Dict_type -Objects/dictobject.c - dictvalues_as_sequence variable static PySequenceMethods dictvalues_as_sequence -Objects/dictobject.c - dictvalues_methods variable static PyMethodDef dictvalues_methods -Objects/dictobject.c - dictviews_as_number variable static PyNumberMethods dictviews_as_number -Modules/posixmodule.c - DirEntry_members variable static PyMemberDef DirEntry_members[] -Modules/posixmodule.c - DirEntry_methods variable static PyMethodDef DirEntry_methods -Modules/posixmodule.c - DirEntryType variable static PyTypeObject DirEntryType -Python/Python-ast.c - Div_singleton variable static PyObject *Div_singleton -Python/Python-ast.c - Div_type variable static PyTypeObject *Div_type -Python/compile.c - __doc__ variable static PyObject *__doc__ -Objects/classobject.c method_get_doc docstr variable static PyObject *docstr -Objects/classobject.c instancemethod_get_doc docstr variable static PyObject *docstr -Python/compile.c compiler_set_qualname dot variable _Py_static_string(dot, ""."") -Python/compile.c compiler_set_qualname dot_locals variable _Py_static_string(dot_locals, "".<locals>"") -Objects/floatobject.c - double_format variable static float_format_type double_format -Modules/itertoolsmodule.c - dropwhile_methods variable static PyMethodDef dropwhile_methods -Modules/itertoolsmodule.c - dropwhile_type variable static PyTypeObject dropwhile_type -Objects/setobject.c - _dummy_struct variable static PyObject _dummy_struct -Modules/posixmodule.c os_dup2_impl dup3_works variable static int dup3_works -Modules/_io/bufferedio.c _PyIO_trap_eintr eintr_int variable static PyObject *eintr_int -Objects/sliceobject.c - ellipsis_methods variable static PyMethodDef ellipsis_methods -Python/hamt.c - _empty_bitmap_node variable static PyHamtNode_Bitmap *_empty_bitmap_node -Objects/setobject.c - emptyfrozenset variable static PyObject *emptyfrozenset -Python/hamt.c - _empty_hamt variable static PyHamtObject *_empty_hamt -Objects/dictobject.c - empty_keys_struct variable static PyDictKeysObject empty_keys_struct -Objects/codeobject.c PyCode_NewEmpty emptystring variable static PyObject *emptystring -Python/compile.c compiler_from_import empty_string variable static PyObject *empty_string -Objects/dictobject.c - empty_values variable static PyObject *empty_values[1] -Objects/unicodeobject.c - encoding_map_methods variable static PyMethodDef encoding_map_methods -Objects/unicodeobject.c - EncodingMapType variable static PyTypeObject EncodingMapType -Objects/enumobject.c - enum_methods variable static PyMethodDef enum_methods -Python/Python-ast.c - Eq_singleton variable static PyObject *Eq_singleton -Python/Python-ast.c - Eq_type variable static PyTypeObject *Eq_type -Objects/exceptions.c - errnomap variable static PyObject *errnomap -Modules/errnomodule.c - errno_methods variable static PyMethodDef errno_methods -Modules/errnomodule.c - errnomodule variable static struct PyModuleDef errnomodule -Modules/_localemodule.c - Error variable static PyObject *Error -Python/Python-ast.c - excepthandler_attributes variable static const char *excepthandler_attributes[] -Python/Python-ast.c - ExceptHandler_fields variable static const char *ExceptHandler_fields[] -Python/Python-ast.c - excepthandler_type variable static PyTypeObject *excepthandler_type -Python/Python-ast.c - ExceptHandler_type variable static PyTypeObject *ExceptHandler_type -Modules/_threadmodule.c - ExceptHookArgs_desc variable static PyStructSequence_Desc ExceptHookArgs_desc -Modules/_threadmodule.c - ExceptHookArgs_fields variable static PyStructSequence_Field ExceptHookArgs_fields[] -Modules/_threadmodule.c - ExceptHookArgsType variable static PyTypeObject ExceptHookArgsType -Objects/exceptions.c _check_for_legacy_statements exec_prefix variable static PyObject *exec_prefix -Python/Python-ast.c - expr_attributes variable static const char *expr_attributes[] -Python/Python-ast.c - expr_context_type variable static PyTypeObject *expr_context_type -Python/Python-ast.c - Expression_fields variable static const char *Expression_fields[] -Python/Python-ast.c - Expression_type variable static PyTypeObject *Expression_type -Python/Python-ast.c - Expr_fields variable static const char *Expr_fields[] -Python/Python-ast.c - expr_type variable static PyTypeObject *expr_type -Python/Python-ast.c - Expr_type variable static PyTypeObject *Expr_type -Python/import.c - extensions variable static PyObject *extensions -Python/Python-ast.c - ExtSlice_fields variable static const char *ExtSlice_fields[] -Python/Python-ast.c - ExtSlice_type variable static PyTypeObject *ExtSlice_type -Objects/boolobject.c - false_str variable static PyObject *false_str -Modules/faulthandler.c - fatal_error variable static struct { int enabled; PyObject *file; int fd; int all_threads; PyInterpreterState *interp; void *exc_handler; } fatal_error -Modules/faulthandler.c - faulthandler_handlers variable static fault_handler_t faulthandler_handlers[] -Objects/stringlib/unicode_format.h - fieldnameiter_methods variable static PyMethodDef fieldnameiter_methods -Modules/_io/fileio.c - fileio_getsetlist variable static PyGetSetDef fileio_getsetlist[] -Modules/_io/fileio.c - fileio_members variable static PyMemberDef fileio_members[] -Modules/_io/fileio.c - fileio_methods variable static PyMethodDef fileio_methods -Modules/itertoolsmodule.c - filterfalse_methods variable static PyMethodDef filterfalse_methods -Modules/itertoolsmodule.c - filterfalse_type variable static PyTypeObject filterfalse_type -Python/bltinmodule.c - filter_methods variable static PyMethodDef filter_methods -Python/sysmodule.c - flags_desc variable static PyStructSequence_Desc flags_desc -Python/sysmodule.c - flags_fields variable static PyStructSequence_Field flags_fields[] -Python/sysmodule.c - FlagsType variable static PyTypeObject FlagsType -Objects/floatobject.c - float_as_number variable static PyNumberMethods float_as_number -Objects/floatobject.c - float_format variable static float_format_type -Objects/floatobject.c - float_getset variable static PyGetSetDef float_getset[] -Objects/floatobject.c - floatinfo_desc variable static PyStructSequence_Desc floatinfo_desc -Objects/floatobject.c - floatinfo_fields variable static PyStructSequence_Field floatinfo_fields[] -Objects/floatobject.c - FloatInfoType variable static PyTypeObject FloatInfoType -Objects/floatobject.c - float_methods variable static PyMethodDef float_methods -Python/Python-ast.c - FloorDiv_singleton variable static PyObject *FloorDiv_singleton -Python/Python-ast.c - FloorDiv_type variable static PyTypeObject *FloorDiv_type -Python/fileutils.c - force_ascii variable static int force_ascii -Python/Python-ast.c - For_fields variable static const char *For_fields[] -Python/Python-ast.c - FormattedValue_fields variable static const char *FormattedValue_fields[] -Python/Python-ast.c - FormattedValue_type variable static PyTypeObject *FormattedValue_type -Objects/stringlib/unicode_format.h - formatteriter_methods variable static PyMethodDef formatteriter_methods -Python/Python-ast.c - For_type variable static PyTypeObject *For_type -Objects/frameobject.c - frame_getsetlist variable static PyGetSetDef frame_getsetlist[] -Objects/frameobject.c - frame_memberlist variable static PyMemberDef frame_memberlist[] -Objects/frameobject.c - frame_methods variable static PyMethodDef frame_methods -Modules/_collectionsmodule.c - freeblocks variable static block *freeblocks[MAXFREEBLOCKS] -Python/dtoa.c - freelist variable static Bigint *freelist[Kmax+1] -Objects/floatobject.c - free_list variable static PyFloatObject *free_list -Objects/frameobject.c - free_list variable static PyFrameObject *free_list -Objects/listobject.c - free_list variable static PyListObject *free_list[PyList_MAXFREELIST] -Objects/dictobject.c - free_list variable static PyDictObject *free_list[PyDict_MAXFREELIST] -Objects/methodobject.c - free_list variable static PyCFunctionObject *free_list -Objects/tupleobject.c - free_list variable static PyTupleObject *free_list[PyTuple_MAXSAVESIZE] -Objects/classobject.c - free_list variable static PyMethodObject *free_list -Objects/setobject.c - frozenset_as_number variable static PyNumberMethods frozenset_as_number -Objects/setobject.c - frozenset_methods variable static PyMethodDef frozenset_methods -Objects/funcobject.c - func_getsetlist variable static PyGetSetDef func_getsetlist[] -Objects/funcobject.c - func_memberlist variable static PyMemberDef func_memberlist[] -Python/Python-ast.c - FunctionDef_fields variable static const char *FunctionDef_fields[] -Python/Python-ast.c - FunctionDef_type variable static PyTypeObject *FunctionDef_type -Modules/_sre.c - _functions variable static PyMethodDef _functions[] -Python/Python-ast.c - FunctionType_fields variable static const char *FunctionType_fields[] -Python/Python-ast.c - FunctionType_type variable static PyTypeObject *FunctionType_type -Modules/_functoolsmodule.c - _functoolsmodule variable static struct PyModuleDef _functoolsmodule -Modules/gcmodule.c - GcMethods variable static PyMethodDef GcMethods[] -Modules/gcmodule.c - gcmodule variable static struct PyModuleDef gcmodule -Modules/gcmodule.c - gc_str variable static PyObject *gc_str -Python/Python-ast.c - GeneratorExp_fields variable static const char *GeneratorExp_fields[] -Python/Python-ast.c - GeneratorExp_type variable static PyTypeObject *GeneratorExp_type -Python/symtable.c - genexpr variable static identifier genexpr -Objects/genobject.c - gen_getsetlist variable static PyGetSetDef gen_getsetlist[] -Objects/genobject.c - gen_memberlist variable static PyMemberDef gen_memberlist[] -Objects/genobject.c - gen_methods variable static PyMethodDef gen_methods -Python/bootstrap_hash.c py_getrandom getrandom_works variable static int getrandom_works -Objects/descrobject.c - getset_getset variable static PyGetSetDef getset_getset[] -Python/Python-ast.c - Global_fields variable static const char *Global_fields[] -Python/Python-ast.c - Global_type variable static PyTypeObject *Global_type -Modules/itertoolsmodule.c - groupby_methods variable static PyMethodDef groupby_methods -Modules/itertoolsmodule.c - groupby_type variable static PyTypeObject groupby_type -Modules/itertoolsmodule.c - _grouper_methods variable static PyMethodDef _grouper_methods -Modules/itertoolsmodule.c - _grouper_type variable static PyTypeObject _grouper_type -Python/Python-ast.c - GtE_singleton variable static PyObject *GtE_singleton -Python/Python-ast.c - GtE_type variable static PyTypeObject *GtE_type -Python/Python-ast.c - Gt_singleton variable static PyObject *Gt_singleton -Python/Python-ast.c - Gt_type variable static PyTypeObject *Gt_type -Modules/signalmodule.c - Handlers variable static volatile struct { _Py_atomic_int tripped; PyObject *func; } Handlers[NSIG] -Python/dynload_shlib.c - handles variable static struct { dev_t dev; ino_t ino; void *handle; } handles[128] -Python/sysmodule.c - hash_info_desc variable static PyStructSequence_Desc hash_info_desc -Python/sysmodule.c - hash_info_fields variable static PyStructSequence_Field hash_info_fields[] -Python/sysmodule.c - Hash_InfoType variable static PyTypeObject Hash_InfoType -Python/import.c import_find_and_load header variable static int header -Python/Python-ast.c - IfExp_fields variable static const char *IfExp_fields[] -Python/Python-ast.c - IfExp_type variable static PyTypeObject *IfExp_type -Python/Python-ast.c - If_fields variable static const char *If_fields[] -Python/Python-ast.c - If_type variable static PyTypeObject *If_type -Modules/signalmodule.c - IgnoreHandler variable static PyObject *IgnoreHandler -Python/import.c - imp_methods variable static PyMethodDef imp_methods -Python/import.c - impmodule variable static struct PyModuleDef impmodule -Objects/exceptions.c - ImportError_members variable static PyMemberDef ImportError_members[] -Objects/exceptions.c - ImportError_methods variable static PyMethodDef ImportError_methods -Python/Python-ast.c - Import_fields variable static const char *Import_fields[] -Python/Python-ast.c - ImportFrom_fields variable static const char *ImportFrom_fields[] -Python/Python-ast.c - ImportFrom_type variable static PyTypeObject *ImportFrom_type -Python/import.c import_find_and_load import_level variable static int import_level -Python/_warnings.c is_internal_frame importlib_string variable static PyObject *importlib_string -Python/import.c - import_lock variable static PyThread_type_lock import_lock -Python/import.c - import_lock_level variable static int import_lock_level -Python/import.c - import_lock_thread variable static unsigned long import_lock_thread -Python/import.c PyImport_Import import_str variable static PyObject *import_str -Python/Python-ast.c - Import_type variable static PyTypeObject *Import_type -Modules/_io/textio.c - incrementalnewlinedecoder_getset variable static PyGetSetDef incrementalnewlinedecoder_getset[] -Modules/_io/textio.c - incrementalnewlinedecoder_methods variable static PyMethodDef incrementalnewlinedecoder_methods -Objects/listobject.c - indexerr variable static PyObject *indexerr -Python/Python-ast.c - Index_fields variable static const char *Index_fields[] -Python/Python-ast.c - Index_type variable static PyTypeObject *Index_type -Python/thread.c - initialized variable static int initialized -Modules/posixmodule.c - initialized variable static int initialized -Modules/pwdmodule.c - initialized variable static int initialized -Modules/signalmodule.c - initialized variable static int initialized -Modules/timemodule.c - initialized variable static int initialized -Python/Python-ast.c init_types initialized variable static int initialized -Objects/listobject.c PyList_New initialized variable static int initialized -Python/import.c - inittab_copy variable static struct _inittab *inittab_copy -Python/Python-ast.c - In_singleton variable static PyObject *In_singleton -Objects/classobject.c - instancemethod_getset variable static PyGetSetDef instancemethod_getset[] -Objects/classobject.c - instancemethod_memberlist variable static PyMemberDef instancemethod_memberlist[] -Python/Python-ast.c - Interactive_fields variable static const char *Interactive_fields[] -Python/Python-ast.c - Interactive_type variable static PyTypeObject *Interactive_type -Objects/unicodeobject.c - interned variable static PyObject *interned -Objects/interpreteridobject.c - interpid_as_number variable static PyNumberMethods interpid_as_number -Modules/signalmodule.c - IntHandler variable static PyObject *IntHandler -Objects/longobject.c - int_info_desc variable static PyStructSequence_Desc int_info_desc -Objects/longobject.c - int_info_fields variable static PyStructSequence_Field int_info_fields[] -Objects/longobject.c - Int_InfoType variable static PyTypeObject Int_InfoType -Python/Python-ast.c - In_type variable static PyTypeObject *In_type -Python/Python-ast.c - Invert_singleton variable static PyObject *Invert_singleton -Python/Python-ast.c - Invert_type variable static PyTypeObject *Invert_type -Modules/_io/iobase.c - iobase_getset variable static PyGetSetDef iobase_getset[] -Modules/_io/iobase.c - iobase_methods variable static PyMethodDef iobase_methods -Python/fileutils.c set_inheritable ioctl_works variable static int ioctl_works -Modules/itertoolsmodule.c - islice_methods variable static PyMethodDef islice_methods -Modules/itertoolsmodule.c - islice_type variable static PyTypeObject islice_type -Python/Python-ast.c - IsNot_singleton variable static PyObject *IsNot_singleton -Python/Python-ast.c - IsNot_type variable static PyTypeObject *IsNot_type -Python/Python-ast.c - Is_singleton variable static PyObject *Is_singleton -Modules/signalmodule.c - is_tripped variable static _Py_atomic_int is_tripped -Python/Python-ast.c - Is_type variable static PyTypeObject *Is_type -Modules/_operator.c - itemgetter_methods variable static PyMethodDef itemgetter_methods -Modules/_operator.c - itemgetter_type variable static PyTypeObject itemgetter_type -Modules/itertoolsmodule.c - itertoolsmodule variable static struct PyModuleDef itertoolsmodule -Modules/signalmodule.c - ItimerError variable static PyObject *ItimerError -Python/Python-ast.c - JoinedStr_fields variable static const char *JoinedStr_fields[] -Python/Python-ast.c - JoinedStr_type variable static PyTypeObject *JoinedStr_type -Modules/_functoolsmodule.c - keyobject_members variable static PyMemberDef keyobject_members[] -Modules/_functoolsmodule.c - keyobject_type variable static PyTypeObject keyobject_type -Objects/dictobject.c - keys_free_list variable static PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST] -Python/Python-ast.c - keyword_fields variable static const char *keyword_fields[] -Python/sysmodule.c sys_set_asyncgen_hooks keywords variable static const char *keywords[] -Modules/_bisectmodule.c bisect_right keywords variable static const char *keywords[] -Modules/_bisectmodule.c insort_right keywords variable static const char *keywords[] -Python/Python-ast.c - keyword_type variable static PyTypeObject *keyword_type -Modules/_functoolsmodule.c keyobject_call kwargs variable static const char *kwargs[] -Modules/_functoolsmodule.c functools_cmp_to_key kwargs variable static const char *kwargs[] -Modules/itertoolsmodule.c repeat_new kwargs variable static const char *kwargs[] -Python/_warnings.c warnings_warn_explicit kwd_list variable static const char *kwd_list[] -Modules/_functoolsmodule.c - kwd_mark variable static PyObject *kwd_mark -Python/bltinmodule.c builtin___import__ kwlist variable static const char *kwlist[] -Python/bltinmodule.c min_max kwlist variable static const char *kwlist[] -Python/context.c contextvar_tp_new kwlist variable static const char *kwlist[] -Python/sysmodule.c sys_getsizeof kwlist variable static const char *kwlist[] -Objects/bytearrayobject.c bytearray_init kwlist variable static const char *kwlist[] -Objects/bytesobject.c bytes_new kwlist variable static const char *kwlist[] -Objects/exceptions.c ImportError_init kwlist variable static const char *kwlist[] -Objects/interpreteridobject.c interpid_new kwlist variable static const char *kwlist[] -Objects/memoryobject.c memory_new kwlist variable static const char *kwlist[] -Objects/memoryobject.c memory_cast kwlist variable static const char *kwlist[] -Objects/memoryobject.c memory_tobytes kwlist variable static const char *kwlist[] -Objects/odictobject.c odict_pop kwlist variable static const char *kwlist[] -Objects/unicodeobject.c unicode_new kwlist variable static const char *kwlist[] -Objects/weakrefobject.c weakref_call kwlist variable static const char *kwlist[] -Modules/_elementtree.c element_setstate_from_Python kwlist variable static const char *kwlist[] -Modules/_json.c scanner_call kwlist variable static const char *kwlist[] -Modules/_json.c scanner_new kwlist variable static const char *kwlist[] -Modules/_json.c encoder_new kwlist variable static const char *kwlist[] -Modules/_json.c encoder_call kwlist variable static const char *kwlist[] -Python/symtable.c - lambda variable static identifier lambda -Python/Python-ast.c - Lambda_fields variable static const char *Lambda_fields[] -Python/Python-ast.c - Lambda_type variable static PyTypeObject *Lambda_type -Objects/listobject.c - list_as_mapping variable static PyMappingMethods list_as_mapping -Objects/listobject.c - list_as_sequence variable static PySequenceMethods list_as_sequence -Python/symtable.c - listcomp variable static identifier listcomp -Python/Python-ast.c - ListComp_fields variable static const char *ListComp_fields[] -Python/Python-ast.c - ListComp_type variable static PyTypeObject *ListComp_type -Python/Python-ast.c - List_fields variable static const char *List_fields[] -Objects/listobject.c - listiter_methods variable static PyMethodDef listiter_methods -Objects/listobject.c - list_methods variable static PyMethodDef list_methods -Objects/listobject.c - listreviter_methods variable static PyMethodDef listreviter_methods -Python/Python-ast.c - List_type variable static PyTypeObject *List_type -Python/ceval.c - lltrace variable static int lltrace -Python/Python-ast.c - Load_singleton variable static PyObject *Load_singleton -Python/Python-ast.c - Load_type variable static PyTypeObject *Load_type -Modules/_threadmodule.c - localdummytype variable static PyTypeObject localdummytype -Modules/_localemodule.c - _localemodule variable static struct PyModuleDef _localemodule -Modules/_threadmodule.c - localtype variable static PyTypeObject localtype -Modules/_threadmodule.c - lock_methods variable static PyMethodDef lock_methods -Modules/_threadmodule.c - Locktype variable static PyTypeObject Locktype -Objects/longobject.c PyLong_FromString log_base_BASE variable static double log_base_BASE[37] -Objects/longobject.c - long_as_number variable static PyNumberMethods long_as_number -Objects/longobject.c - long_getset variable static PyGetSetDef long_getset[] -Objects/longobject.c - long_methods variable static PyMethodDef long_methods -Objects/rangeobject.c - longrangeiter_methods variable static PyMethodDef longrangeiter_methods -Modules/_functoolsmodule.c - lru_cache_getsetlist variable static PyGetSetDef lru_cache_getsetlist[] -Modules/_functoolsmodule.c - lru_cache_methods variable static PyMethodDef lru_cache_methods -Modules/_functoolsmodule.c - lru_cache_type variable static PyTypeObject lru_cache_type -Modules/_functoolsmodule.c - lru_list_elem_type variable static PyTypeObject lru_list_elem_type -Python/Python-ast.c - LShift_singleton variable static PyObject *LShift_singleton -Python/Python-ast.c - LShift_type variable static PyTypeObject *LShift_type -Python/Python-ast.c - LtE_singleton variable static PyObject *LtE_singleton -Python/Python-ast.c - LtE_type variable static PyTypeObject *LtE_type -Python/Python-ast.c - Lt_singleton variable static PyObject *Lt_singleton -Python/Python-ast.c - Lt_type variable static PyTypeObject *Lt_type -Python/bltinmodule.c - map_methods variable static PyMethodDef map_methods -Objects/descrobject.c - mappingproxy_as_mapping variable static PyMappingMethods mappingproxy_as_mapping -Objects/descrobject.c - mappingproxy_as_sequence variable static PySequenceMethods mappingproxy_as_sequence -Objects/descrobject.c - mappingproxy_methods variable static PyMethodDef mappingproxy_methods -Objects/dictobject.c - mapp_methods variable static PyMethodDef mapp_methods -Python/marshal.c - marshal_methods variable static PyMethodDef marshal_methods -Python/marshal.c - marshalmodule variable static struct PyModuleDef marshalmodule -Modules/_sre.c - match_as_mapping variable static PyMappingMethods match_as_mapping -Modules/_sre.c - match_getset variable static PyGetSetDef match_getset[] -Modules/_sre.c - match_members variable static PyMemberDef match_members[] -Modules/_sre.c - match_methods variable static PyMethodDef match_methods -Modules/_sre.c - Match_Type variable static PyTypeObject Match_Type -Python/Python-ast.c - MatMult_singleton variable static PyObject *MatMult_singleton -Python/Python-ast.c - MatMult_type variable static PyTypeObject *MatMult_type -Objects/obmalloc.c - maxarenas variable static uint maxarenas -Objects/moduleobject.c - max_module_number variable static Py_ssize_t max_module_number -Objects/descrobject.c - member_getset variable static PyGetSetDef member_getset[] -Objects/exceptions.c - memerrors_freelist variable static PyBaseExceptionObject *memerrors_freelist -Objects/exceptions.c - memerrors_numfree variable static int memerrors_numfree -Objects/memoryobject.c - memory_as_buffer variable static PyBufferProcs memory_as_buffer -Objects/memoryobject.c - memory_as_mapping variable static PyMappingMethods memory_as_mapping -Objects/memoryobject.c - memory_as_sequence variable static PySequenceMethods memory_as_sequence -Objects/memoryobject.c - memory_getsetlist variable static PyGetSetDef memory_getsetlist[] -Objects/memoryobject.c - memory_methods variable static PyMethodDef memory_methods -Objects/methodobject.c - meth_getsets variable static PyGetSetDef meth_getsets [] -Objects/methodobject.c - meth_members variable static PyMemberDef meth_members[] -Objects/methodobject.c - meth_methods variable static PyMethodDef meth_methods -Objects/typeobject.c - method_cache variable static struct method_cache_entry method_cache[1 << MCACHE_SIZE_EXP] -Modules/_operator.c - methodcaller_methods variable static PyMethodDef methodcaller_methods -Modules/_operator.c - methodcaller_type variable static PyTypeObject methodcaller_type -Objects/classobject.c - method_getset variable static PyGetSetDef method_getset[] -Objects/descrobject.c - method_getset variable static PyGetSetDef method_getset[] -Objects/classobject.c - method_memberlist variable static PyMemberDef method_memberlist[] -Objects/classobject.c - method_methods variable static PyMethodDef method_methods -Python/codecs.c _PyCodecRegistry_Init methods variable static struct { char *name; PyMethodDef def; } methods[] -Python/frozen.c - M___hello__ variable static unsigned char M___hello__[] -Python/Python-ast.c - Mod_singleton variable static PyObject *Mod_singleton -Python/Python-ast.c - mod_type variable static PyTypeObject *mod_type -Python/Python-ast.c - Mod_type variable static PyTypeObject *Mod_type -Modules/faulthandler.c - module_def variable static struct PyModuleDef module_def -Modules/_tracemalloc.c - module_def variable static struct PyModuleDef module_def -Python/Python-ast.c - Module_fields variable static const char *Module_fields[] -Modules/_collectionsmodule.c - module_functions variable static struct PyMethodDef module_functions[] -Modules/_abc.c - module_functions variable static struct PyMethodDef module_functions[] -Objects/moduleobject.c - module_members variable static PyMemberDef module_members[] -Objects/moduleobject.c - module_methods variable static PyMethodDef module_methods -Modules/_functoolsmodule.c - module_methods variable static PyMethodDef module_methods -Modules/itertoolsmodule.c - module_methods variable static PyMethodDef module_methods -Modules/_io/_iomodule.c - module_methods variable static PyMethodDef module_methods -Modules/faulthandler.c - module_methods variable static PyMethodDef module_methods -Modules/_tracemalloc.c - module_methods variable static PyMethodDef module_methods -Python/Python-ast.c - Module_type variable static PyTypeObject *Module_type -Python/Python-ast.c - Mult_singleton variable static PyObject *Mult_singleton -Python/Python-ast.c - Mult_type variable static PyTypeObject *Mult_type -Objects/funcobject.c PyFunction_NewWithQualName __name__ variable static PyObject *__name__ -Python/compile.c compiler_lambda name variable static identifier name -Python/compile.c compiler_genexp name variable static identifier name -Python/compile.c compiler_listcomp name variable static identifier name -Python/compile.c compiler_setcomp name variable static identifier name -Python/compile.c compiler_dictcomp name variable static identifier name -Python/Python-ast.c - NamedExpr_fields variable static const char *NamedExpr_fields[] -Python/Python-ast.c - NamedExpr_type variable static PyTypeObject *NamedExpr_type -Python/Python-ast.c - Name_fields variable static const char *Name_fields[] -Objects/typeobject.c - name_op variable static _Py_Identifier name_op[] -Objects/namespaceobject.c - namespace_members variable static PyMemberDef namespace_members[] -Objects/namespaceobject.c - namespace_methods variable static PyMethodDef namespace_methods -Python/Python-ast.c - Name_type variable static PyTypeObject *Name_type -Objects/obmalloc.c - narenas_currently_allocated variable static size_t narenas_currently_allocated -Objects/obmalloc.c - narenas_highwater variable static size_t narenas_highwater -Python/sysmodule.c sys_displayhook newline variable static PyObject *newline -Objects/typeobject.c - next_version_tag variable static unsigned int next_version_tag -Objects/obmalloc.c - nfp2lasta variable static struct arena_object* nfp2lasta[MAX_POOLS_IN_ARENA + 1] -Python/dynload_shlib.c - nhandles variable static int nhandles -Objects/object.c - none_as_number variable static PyNumberMethods none_as_number -Python/Python-ast.c - Nonlocal_fields variable static const char *Nonlocal_fields[] -Python/Python-ast.c - Nonlocal_type variable static PyTypeObject *Nonlocal_type -Python/Python-ast.c - NotEq_singleton variable static PyObject *NotEq_singleton -Python/Python-ast.c - NotEq_type variable static PyTypeObject *NotEq_type -Objects/object.c - notimplemented_methods variable static PyMethodDef notimplemented_methods -Python/Python-ast.c - NotIn_singleton variable static PyObject *NotIn_singleton -Python/Python-ast.c - NotIn_type variable static PyTypeObject *NotIn_type -Python/Python-ast.c - Not_singleton variable static PyObject *Not_singleton -Python/Python-ast.c - Not_type variable static PyTypeObject *Not_type -Objects/obmalloc.c - ntimes_arena_allocated variable static size_t ntimes_arena_allocated -Objects/bytesobject.c - nullstring variable static PyBytesObject *nullstring -Objects/codeobject.c PyCode_NewEmpty nulltuple variable static PyObject *nulltuple -Objects/floatobject.c - numfree variable static int numfree -Objects/frameobject.c - numfree variable static int numfree -Objects/listobject.c - numfree variable static int numfree -Objects/dictobject.c - numfree variable static int numfree -Objects/methodobject.c - numfree variable static int numfree -Objects/tupleobject.c - numfree variable static int numfree[PyTuple_MAXSAVESIZE] -Objects/classobject.c - numfree variable static int numfree -Modules/_collectionsmodule.c - numfreeblocks variable static Py_ssize_t numfreeblocks -Objects/dictobject.c - numfreekeys variable static int numfreekeys -Objects/typeobject.c - object_getsets variable static PyGetSetDef object_getsets[] -Objects/typeobject.c - object_methods variable static PyMethodDef object_methods -Objects/typeobject.c object___reduce_ex___impl objreduce variable static PyObject *objreduce -Objects/odictobject.c - odict_as_mapping variable static PyMappingMethods odict_as_mapping -Objects/odictobject.c - odict_getset variable static PyGetSetDef odict_getset[] -Objects/odictobject.c - odictitems_methods variable static PyMethodDef odictitems_methods -Objects/odictobject.c - odictiter_methods variable static PyMethodDef odictiter_methods -Objects/odictobject.c - odictkeys_methods variable static PyMethodDef odictkeys_methods -Objects/odictobject.c - odict_methods variable static PyMethodDef odict_methods -Objects/odictobject.c - odictvalues_methods variable static PyMethodDef odictvalues_methods -Modules/faulthandler.c - old_stack variable static stack_t old_stack -Modules/_operator.c - operator_methods variable static PyMethodDef operator_methods -Modules/_operator.c - operatormodule variable static struct PyModuleDef operatormodule -Python/Python-ast.c - operator_type variable static PyTypeObject *operator_type -Objects/typeobject.c slot_nb_add op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_subtract op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_multiply op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_matrix_multiply op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_remainder op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_divmod op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_power_binary op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_lshift op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_rshift op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_and op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_xor op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_or op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_floor_divide op_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_true_divide op_id variable _Py_static_string(op_id, OPSTR) -Python/getopt.c - opt_ptr variable static const wchar_t *opt_ptr -Python/initconfig.c - orig_argv variable static PyWideStringList orig_argv -Python/Python-ast.c - Or_singleton variable static PyObject *Or_singleton -Python/Python-ast.c - Or_type variable static PyTypeObject *Or_type -Objects/exceptions.c - OSError_getset variable static PyGetSetDef OSError_getset[] -Objects/exceptions.c - OSError_members variable static PyMemberDef OSError_members[] -Objects/exceptions.c - OSError_methods variable static PyMethodDef OSError_methods -Python/dtoa.c - p5s variable static Bigint *p5s -Python/Python-ast.c - Param_singleton variable static PyObject *Param_singleton -Python/Python-ast.c - Param_type variable static PyTypeObject *Param_type -Python/bltinmodule.c builtin_print _parser variable static struct _PyArg_Parser _parser -Python/clinic/_warnings.c.h warnings_warn _parser variable static _PyArg_Parser _parser -Python/clinic/bltinmodule.c.h builtin_compile _parser variable static _PyArg_Parser _parser -Python/clinic/bltinmodule.c.h builtin_round _parser variable static _PyArg_Parser _parser -Python/clinic/bltinmodule.c.h builtin_sum _parser variable static _PyArg_Parser _parser -Python/clinic/import.c.h _imp_source_hash _parser variable static _PyArg_Parser _parser -Python/clinic/sysmodule.c.h sys_addaudithook _parser variable static _PyArg_Parser _parser -Python/clinic/sysmodule.c.h sys_set_coroutine_origin_tracking_depth _parser variable static _PyArg_Parser _parser -Python/clinic/traceback.c.h tb_new _parser variable static _PyArg_Parser _parser -Objects/clinic/bytearrayobject.c.h bytearray_translate _parser variable static _PyArg_Parser _parser -Objects/clinic/bytearrayobject.c.h bytearray_split _parser variable static _PyArg_Parser _parser -Objects/clinic/bytearrayobject.c.h bytearray_rsplit _parser variable static _PyArg_Parser _parser -Objects/clinic/bytearrayobject.c.h bytearray_decode _parser variable static _PyArg_Parser _parser -Objects/clinic/bytearrayobject.c.h bytearray_splitlines _parser variable static _PyArg_Parser _parser -Objects/clinic/bytearrayobject.c.h bytearray_hex _parser variable static _PyArg_Parser _parser -Objects/clinic/bytesobject.c.h bytes_split _parser variable static _PyArg_Parser _parser -Objects/clinic/bytesobject.c.h bytes_rsplit _parser variable static _PyArg_Parser _parser -Objects/clinic/bytesobject.c.h bytes_translate _parser variable static _PyArg_Parser _parser -Objects/clinic/bytesobject.c.h bytes_decode _parser variable static _PyArg_Parser _parser -Objects/clinic/bytesobject.c.h bytes_splitlines _parser variable static _PyArg_Parser _parser -Objects/clinic/bytesobject.c.h bytes_hex _parser variable static _PyArg_Parser _parser -Objects/clinic/codeobject.c.h code_replace _parser variable static _PyArg_Parser _parser -Objects/clinic/complexobject.c.h complex_new _parser variable static _PyArg_Parser _parser -Objects/clinic/descrobject.c.h mappingproxy_new _parser variable static _PyArg_Parser _parser -Objects/clinic/descrobject.c.h property_init _parser variable static _PyArg_Parser _parser -Objects/clinic/enumobject.c.h enum_new _parser variable static _PyArg_Parser _parser -Objects/clinic/funcobject.c.h func_new _parser variable static _PyArg_Parser _parser -Objects/clinic/listobject.c.h list_sort _parser variable static _PyArg_Parser _parser -Objects/clinic/longobject.c.h long_new _parser variable static _PyArg_Parser _parser -Objects/clinic/longobject.c.h int_to_bytes _parser variable static _PyArg_Parser _parser -Objects/clinic/longobject.c.h int_from_bytes _parser variable static _PyArg_Parser _parser -Objects/clinic/memoryobject.c.h memoryview_hex _parser variable static _PyArg_Parser _parser -Objects/clinic/moduleobject.c.h module___init__ _parser variable static _PyArg_Parser _parser -Objects/clinic/odictobject.c.h OrderedDict_fromkeys _parser variable static _PyArg_Parser _parser -Objects/clinic/odictobject.c.h OrderedDict_setdefault _parser variable static _PyArg_Parser _parser -Objects/clinic/odictobject.c.h OrderedDict_popitem _parser variable static _PyArg_Parser _parser -Objects/clinic/odictobject.c.h OrderedDict_move_to_end _parser variable static _PyArg_Parser _parser -Objects/clinic/structseq.c.h structseq_new _parser variable static _PyArg_Parser _parser -Objects/clinic/unicodeobject.c.h unicode_encode _parser variable static _PyArg_Parser _parser -Objects/clinic/unicodeobject.c.h unicode_expandtabs _parser variable static _PyArg_Parser _parser -Objects/clinic/unicodeobject.c.h unicode_split _parser variable static _PyArg_Parser _parser -Objects/clinic/unicodeobject.c.h unicode_rsplit _parser variable static _PyArg_Parser _parser -Objects/clinic/unicodeobject.c.h unicode_splitlines _parser variable static _PyArg_Parser _parser -Objects/stringlib/clinic/transmogrify.h.h stringlib_expandtabs _parser variable static _PyArg_Parser _parser -Modules/_blake2/clinic/blake2b_impl.c.h py_blake2b_new _parser variable static _PyArg_Parser _parser -Modules/_blake2/clinic/blake2s_impl.c.h py_blake2s_new _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/_iomodule.c.h _io_open _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/_iomodule.c.h _io_open_code _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/bufferedio.c.h _io_BufferedReader___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/bufferedio.c.h _io_BufferedWriter___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/bufferedio.c.h _io_BufferedRandom___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/bytesio.c.h _io_BytesIO___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/fileio.c.h _io_FileIO___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/stringio.c.h _io_StringIO___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/textio.c.h _io_IncrementalNewlineDecoder___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/textio.c.h _io_IncrementalNewlineDecoder_decode _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/textio.c.h _io_TextIOWrapper___init__ _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/textio.c.h _io_TextIOWrapper_reconfigure _parser variable static _PyArg_Parser _parser -Modules/_io/clinic/winconsoleio.c.h _io__WindowsConsoleIO___init__ _parser variable static _PyArg_Parser _parser -Modules/_multiprocessing/clinic/posixshmem.c.h _posixshmem_shm_open _parser variable static _PyArg_Parser _parser -Modules/_multiprocessing/clinic/posixshmem.c.h _posixshmem_shm_unlink _parser variable static _PyArg_Parser _parser -Modules/cjkcodecs/clinic/multibytecodec.c.h _multibytecodec_MultibyteCodec_encode _parser variable static _PyArg_Parser _parser -Modules/cjkcodecs/clinic/multibytecodec.c.h _multibytecodec_MultibyteCodec_decode _parser variable static _PyArg_Parser _parser -Modules/cjkcodecs/clinic/multibytecodec.c.h _multibytecodec_MultibyteIncrementalEncoder_encode _parser variable static _PyArg_Parser _parser -Modules/cjkcodecs/clinic/multibytecodec.c.h _multibytecodec_MultibyteIncrementalDecoder_decode _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio_Future___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio_Future_add_done_callback _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio_Task___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio_Task_current_task _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio_Task_all_tasks _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio_Task_get_stack _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio_Task_print_stack _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio__register_task _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio__unregister_task _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio__enter_task _parser variable static _PyArg_Parser _parser -Modules/clinic/_asynciomodule.c.h _asyncio__leave_task _parser variable static _PyArg_Parser _parser -Modules/clinic/_bz2module.c.h _bz2_BZ2Decompressor_decompress _parser variable static _PyArg_Parser _parser -Modules/clinic/_codecsmodule.c.h _codecs_encode _parser variable static _PyArg_Parser _parser -Modules/clinic/_codecsmodule.c.h _codecs_decode _parser variable static _PyArg_Parser _parser -Modules/clinic/_cursesmodule.c.h _curses_setupterm _parser variable static _PyArg_Parser _parser -Modules/clinic/_datetimemodule.c.h datetime_datetime_now _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_Element_find _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_Element_findtext _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_Element_findall _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_Element_iterfind _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_Element_get _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_Element_iter _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_Element_getiterator _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_TreeBuilder___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_elementtree.c.h _elementtree_XMLParser___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_hashopenssl.c.h EVP_new _parser variable static _PyArg_Parser _parser -Modules/clinic/_hashopenssl.c.h pbkdf2_hmac _parser variable static _PyArg_Parser _parser -Modules/clinic/_hashopenssl.c.h _hashlib_scrypt _parser variable static _PyArg_Parser _parser -Modules/clinic/_hashopenssl.c.h _hashlib_hmac_digest _parser variable static _PyArg_Parser _parser -Modules/clinic/_lzmamodule.c.h _lzma_LZMADecompressor_decompress _parser variable static _PyArg_Parser _parser -Modules/clinic/_lzmamodule.c.h _lzma_LZMADecompressor___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_opcode.c.h _opcode_stack_effect _parser variable static _PyArg_Parser _parser -Modules/clinic/_pickle.c.h _pickle_Pickler___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_pickle.c.h _pickle_Unpickler___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_pickle.c.h _pickle_dump _parser variable static _PyArg_Parser _parser -Modules/clinic/_pickle.c.h _pickle_dumps _parser variable static _PyArg_Parser _parser -Modules/clinic/_pickle.c.h _pickle_load _parser variable static _PyArg_Parser _parser -Modules/clinic/_pickle.c.h _pickle_loads _parser variable static _PyArg_Parser _parser -Modules/clinic/_queuemodule.c.h _queue_SimpleQueue_put _parser variable static _PyArg_Parser _parser -Modules/clinic/_queuemodule.c.h _queue_SimpleQueue_put_nowait _parser variable static _PyArg_Parser _parser -Modules/clinic/_queuemodule.c.h _queue_SimpleQueue_get _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_match _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_fullmatch _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_search _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_findall _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_finditer _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_scanner _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_split _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_sub _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Pattern_subn _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_compile _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Match_expand _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Match_groups _parser variable static _PyArg_Parser _parser -Modules/clinic/_sre.c.h _sre_SRE_Match_groupdict _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl__SSLSocket_get_channel_binding _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl__SSLContext_load_cert_chain _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl__SSLContext_load_verify_locations _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl__SSLContext__wrap_socket _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl__SSLContext__wrap_bio _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl__SSLContext_get_ca_certs _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl_txt2obj _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl_enum_certificates _parser variable static _PyArg_Parser _parser -Modules/clinic/_ssl.c.h _ssl_enum_crls _parser variable static _PyArg_Parser _parser -Modules/clinic/_struct.c.h Struct___init__ _parser variable static _PyArg_Parser _parser -Modules/clinic/_struct.c.h Struct_unpack_from _parser variable static _PyArg_Parser _parser -Modules/clinic/_struct.c.h unpack_from _parser variable static _PyArg_Parser _parser -Modules/clinic/_winapi.c.h _winapi_ConnectNamedPipe _parser variable static _PyArg_Parser _parser -Modules/clinic/_winapi.c.h _winapi_ReadFile _parser variable static _PyArg_Parser _parser -Modules/clinic/_winapi.c.h _winapi_WriteFile _parser variable static _PyArg_Parser _parser -Modules/clinic/_winapi.c.h _winapi_GetFileType _parser variable static _PyArg_Parser _parser -Modules/clinic/binascii.c.h binascii_b2a_uu _parser variable static _PyArg_Parser _parser -Modules/clinic/binascii.c.h binascii_b2a_base64 _parser variable static _PyArg_Parser _parser -Modules/clinic/binascii.c.h binascii_b2a_hex _parser variable static _PyArg_Parser _parser -Modules/clinic/binascii.c.h binascii_hexlify _parser variable static _PyArg_Parser _parser -Modules/clinic/binascii.c.h binascii_a2b_qp _parser variable static _PyArg_Parser _parser -Modules/clinic/binascii.c.h binascii_b2a_qp _parser variable static _PyArg_Parser _parser -Modules/clinic/cmathmodule.c.h cmath_isclose _parser variable static _PyArg_Parser _parser -Modules/clinic/gcmodule.c.h gc_collect _parser variable static _PyArg_Parser _parser -Modules/clinic/gcmodule.c.h gc_get_objects _parser variable static _PyArg_Parser _parser -Modules/clinic/grpmodule.c.h grp_getgrgid _parser variable static _PyArg_Parser _parser -Modules/clinic/grpmodule.c.h grp_getgrnam _parser variable static _PyArg_Parser _parser -Modules/_functoolsmodule.c - partial_getsetlist variable static PyGetSetDef partial_getsetlist[] -Modules/_functoolsmodule.c - partial_memberlist variable static PyMemberDef partial_memberlist[] -Modules/_functoolsmodule.c - partial_methods variable static PyMethodDef partial_methods -Modules/_functoolsmodule.c - partial_type variable static PyTypeObject partial_type -Python/Python-ast.c - Pass_type variable static PyTypeObject *Pass_type -Modules/_sre.c - pattern_getset variable static PyGetSetDef pattern_getset[] -Modules/_sre.c - pattern_members variable static PyMemberDef pattern_members[] -Modules/_sre.c - pattern_methods variable static PyMethodDef pattern_methods -Modules/_sre.c - Pattern_Type variable static PyTypeObject Pattern_Type -Modules/itertoolsmodule.c - permuations_methods variable static PyMethodDef permuations_methods -Modules/itertoolsmodule.c - permutations_type variable static PyTypeObject permutations_type -Objects/picklebufobject.c - picklebuf_as_buffer variable static PyBufferProcs picklebuf_as_buffer -Objects/picklebufobject.c - picklebuf_methods variable static PyMethodDef picklebuf_methods -Python/dtoa.c - pmem_next variable static double *pmem_next -Objects/typeobject.c resolve_slotdups pname variable static PyObject *pname -Modules/posixmodule.c - posix_constants_confstr variable static struct constdef posix_constants_confstr[] -Modules/posixmodule.c - posix_constants_pathconf variable static struct constdef posix_constants_pathconf[] -Modules/posixmodule.c - posix_constants_sysconf variable static struct constdef posix_constants_sysconf[] -Modules/posixmodule.c - posix_methods variable static PyMethodDef posix_methods -Modules/posixmodule.c - posixmodule variable static struct PyModuleDef posixmodule -Modules/posixmodule.c - posix_putenv_garbage variable static PyObject *posix_putenv_garbage -Python/Python-ast.c - Pow_singleton variable static PyObject *Pow_singleton -Python/Python-ast.c - Pow_type variable static PyTypeObject *Pow_type -Python/sysmodule.c - _preinit_warnoptions variable static _Py_PreInitEntry _preinit_warnoptions -Python/sysmodule.c - _preinit_xoptions variable static _Py_PreInitEntry _preinit_xoptions -Objects/exceptions.c _check_for_legacy_statements print_prefix variable static PyObject *print_prefix -Python/dtoa.c - private_mem variable static double private_mem[PRIVATE_mem] -Modules/itertoolsmodule.c - product_methods variable static PyMethodDef product_methods -Modules/itertoolsmodule.c - product_type variable static PyTypeObject product_type -Objects/descrobject.c - property_getsetlist variable static PyGetSetDef property_getsetlist[] -Objects/descrobject.c - property_members variable static PyMemberDef property_members[] -Objects/descrobject.c - property_methods variable static PyMethodDef property_methods -Objects/weakrefobject.c - proxy_as_mapping variable static PyMappingMethods proxy_as_mapping -Objects/weakrefobject.c - proxy_as_number variable static PyNumberMethods proxy_as_number -Objects/weakrefobject.c - proxy_as_sequence variable static PySequenceMethods proxy_as_sequence -Objects/weakrefobject.c - proxy_methods variable static PyMethodDef proxy_methods -Objects/typeobject.c resolve_slotdups ptrs variable static slotdef *ptrs[MAX_EQUIV] -Modules/pwdmodule.c - pwd_methods variable static PyMethodDef pwd_methods -Modules/pwdmodule.c - pwdmodule variable static struct PyModuleDef pwdmodule -Objects/obmalloc.c - _Py_AllocatedBlocks variable static Py_ssize_t _Py_AllocatedBlocks -Objects/genobject.c - _PyAsyncGenASend_Type variable PyTypeObject _PyAsyncGenASend_Type -Objects/genobject.c - _PyAsyncGenAThrow_Type variable PyTypeObject _PyAsyncGenAThrow_Type -Objects/genobject.c - PyAsyncGen_Type variable PyTypeObject PyAsyncGen_Type -Objects/genobject.c - _PyAsyncGenWrappedValue_Type variable PyTypeObject _PyAsyncGenWrappedValue_Type -Objects/typeobject.c - PyBaseObject_Type variable PyTypeObject PyBaseObject_Type -Modules/_blake2/blake2b_impl.c - PyBlake2_BLAKE2bType variable PyTypeObject PyBlake2_BLAKE2bType -Modules/_blake2/blake2s_impl.c - PyBlake2_BLAKE2sType variable PyTypeObject PyBlake2_BLAKE2sType -Objects/boolobject.c - PyBool_Type variable PyTypeObject PyBool_Type -Modules/_io/bufferedio.c - PyBufferedIOBase_Type variable PyTypeObject PyBufferedIOBase_Type -Modules/_io/bufferedio.c - PyBufferedRandom_Type variable PyTypeObject PyBufferedRandom_Type -Modules/_io/bufferedio.c - PyBufferedReader_Type variable PyTypeObject PyBufferedReader_Type -Modules/_io/bufferedio.c - PyBufferedRWPair_Type variable PyTypeObject PyBufferedRWPair_Type -Modules/_io/bufferedio.c - PyBufferedWriter_Type variable PyTypeObject PyBufferedWriter_Type -Objects/bytearrayobject.c - _PyByteArray_empty_string variable char _PyByteArray_empty_string[] -Objects/bytearrayobject.c - PyByteArrayIter_Type variable PyTypeObject PyByteArrayIter_Type -Objects/bytearrayobject.c - PyByteArray_Type variable PyTypeObject PyByteArray_Type -Modules/_io/bytesio.c - _PyBytesIOBuffer_Type variable PyTypeObject _PyBytesIOBuffer_Type -Modules/_io/bytesio.c - PyBytesIO_Type variable PyTypeObject PyBytesIO_Type -Objects/bytesobject.c - PyBytesIter_Type variable PyTypeObject PyBytesIter_Type -Objects/bytesobject.c - PyBytes_Type variable PyTypeObject PyBytes_Type -Python/initconfig.c - Py_BytesWarningFlag variable int Py_BytesWarningFlag -Objects/iterobject.c - PyCallIter_Type variable PyTypeObject PyCallIter_Type -Objects/capsule.c - PyCapsule_Type variable PyTypeObject PyCapsule_Type -Objects/cellobject.c - PyCell_Type variable PyTypeObject PyCell_Type -Objects/methodobject.c - PyCFunction_Type variable PyTypeObject PyCFunction_Type -Objects/descrobject.c - PyClassMethodDescr_Type variable PyTypeObject PyClassMethodDescr_Type -Objects/funcobject.c - PyClassMethod_Type variable PyTypeObject PyClassMethod_Type -Objects/codeobject.c - PyCode_Type variable PyTypeObject PyCode_Type -Objects/complexobject.c - PyComplex_Type variable PyTypeObject PyComplex_Type -Python/context.c - PyContext_as_mapping variable static PyMappingMethods PyContext_as_mapping -Python/context.c - PyContext_as_sequence variable static PySequenceMethods PyContext_as_sequence -Python/context.c - PyContext_methods variable static PyMethodDef PyContext_methods -Python/context.c - PyContextTokenMissing_Type variable PyTypeObject PyContextTokenMissing_Type -Python/context.c - PyContextToken_Type variable PyTypeObject PyContextToken_Type -Python/context.c - PyContextTokenType_getsetlist variable static PyGetSetDef PyContextTokenType_getsetlist[] -Python/context.c - PyContext_Type variable PyTypeObject PyContext_Type -Python/context.c - PyContextVar_members variable static PyMemberDef PyContextVar_members[] -Python/context.c - PyContextVar_methods variable static PyMethodDef PyContextVar_methods -Python/context.c - PyContextVar_Type variable PyTypeObject PyContextVar_Type -Objects/genobject.c - PyCoro_Type variable PyTypeObject PyCoro_Type -Objects/genobject.c - _PyCoroWrapper_Type variable PyTypeObject _PyCoroWrapper_Type -Python/initconfig.c - Py_DebugFlag variable int Py_DebugFlag -Objects/dictobject.c - pydict_global_version variable static uint64_t pydict_global_version -Objects/dictobject.c - PyDictItems_Type variable PyTypeObject PyDictItems_Type -Objects/dictobject.c - PyDictIterItem_Type variable PyTypeObject PyDictIterItem_Type -Objects/dictobject.c - PyDictIterKey_Type variable PyTypeObject PyDictIterKey_Type -Objects/dictobject.c - PyDictIterValue_Type variable PyTypeObject PyDictIterValue_Type -Objects/dictobject.c - PyDictKeys_Type variable PyTypeObject PyDictKeys_Type -Objects/descrobject.c - PyDictProxy_Type variable PyTypeObject PyDictProxy_Type -Objects/dictobject.c - PyDictRevIterItem_Type variable PyTypeObject PyDictRevIterItem_Type -Objects/dictobject.c - PyDictRevIterKey_Type variable PyTypeObject PyDictRevIterKey_Type -Objects/dictobject.c - PyDictRevIterValue_Type variable PyTypeObject PyDictRevIterValue_Type -Objects/dictobject.c - PyDict_Type variable PyTypeObject PyDict_Type -Objects/dictobject.c - PyDictValues_Type variable PyTypeObject PyDictValues_Type -Python/initconfig.c - Py_DontWriteBytecodeFlag variable int Py_DontWriteBytecodeFlag -Objects/sliceobject.c - _Py_EllipsisObject variable PyObject _Py_EllipsisObject -Objects/sliceobject.c - PyEllipsis_Type variable PyTypeObject PyEllipsis_Type -Objects/enumobject.c - PyEnum_Type variable PyTypeObject PyEnum_Type -Objects/exceptions.c - _PyExc_ArithmeticError variable static PyTypeObject _PyExc_ArithmeticError -Objects/exceptions.c - PyExc_ArithmeticError variable static PyTypeObject PyExc_ArithmeticError -Objects/exceptions.c - _PyExc_AssertionError variable static PyTypeObject _PyExc_AssertionError -Objects/exceptions.c - PyExc_AssertionError variable static PyTypeObject PyExc_AssertionError -Objects/exceptions.c - _PyExc_AttributeError variable static PyTypeObject _PyExc_AttributeError -Objects/exceptions.c - PyExc_AttributeError variable static PyTypeObject PyExc_AttributeError -Objects/exceptions.c - _PyExc_BaseException variable static PyTypeObject _PyExc_BaseException -Objects/exceptions.c - PyExc_BaseException variable static PyTypeObject PyExc_BaseException -Objects/exceptions.c - _PyExc_BlockingIOError variable static PyTypeObject _PyExc_BlockingIOError -Objects/exceptions.c - PyExc_BlockingIOError variable static PyTypeObject PyExc_BlockingIOError -Objects/exceptions.c - _PyExc_BrokenPipeError variable static PyTypeObject _PyExc_BrokenPipeError -Objects/exceptions.c - PyExc_BrokenPipeError variable static PyTypeObject PyExc_BrokenPipeError -Objects/exceptions.c - _PyExc_BufferError variable static PyTypeObject _PyExc_BufferError -Objects/exceptions.c - PyExc_BufferError variable static PyTypeObject PyExc_BufferError -Objects/exceptions.c - _PyExc_BytesWarning variable static PyTypeObject _PyExc_BytesWarning -Objects/exceptions.c - PyExc_BytesWarning variable static PyTypeObject PyExc_BytesWarning -Objects/exceptions.c - _PyExc_ChildProcessError variable static PyTypeObject _PyExc_ChildProcessError -Objects/exceptions.c - PyExc_ChildProcessError variable static PyTypeObject PyExc_ChildProcessError -Objects/exceptions.c - _PyExc_ConnectionAbortedError variable static PyTypeObject _PyExc_ConnectionAbortedError -Objects/exceptions.c - PyExc_ConnectionAbortedError variable static PyTypeObject PyExc_ConnectionAbortedError -Objects/exceptions.c - _PyExc_ConnectionError variable static PyTypeObject _PyExc_ConnectionError -Objects/exceptions.c - PyExc_ConnectionError variable static PyTypeObject PyExc_ConnectionError -Objects/exceptions.c - _PyExc_ConnectionRefusedError variable static PyTypeObject _PyExc_ConnectionRefusedError -Objects/exceptions.c - PyExc_ConnectionRefusedError variable static PyTypeObject PyExc_ConnectionRefusedError -Objects/exceptions.c - _PyExc_ConnectionResetError variable static PyTypeObject _PyExc_ConnectionResetError -Objects/exceptions.c - PyExc_ConnectionResetError variable static PyTypeObject PyExc_ConnectionResetError -Objects/exceptions.c - _PyExc_DeprecationWarning variable static PyTypeObject _PyExc_DeprecationWarning -Objects/exceptions.c - PyExc_DeprecationWarning variable static PyTypeObject PyExc_DeprecationWarning -Objects/exceptions.c - PyExc_EnvironmentError variable static PyTypeObject PyExc_EnvironmentError -Objects/exceptions.c - _PyExc_EOFError variable static PyTypeObject _PyExc_EOFError -Objects/exceptions.c - PyExc_EOFError variable static PyTypeObject PyExc_EOFError -Objects/exceptions.c - _PyExc_Exception variable static PyTypeObject _PyExc_Exception -Objects/exceptions.c - PyExc_Exception variable static PyTypeObject PyExc_Exception -Objects/exceptions.c - _PyExc_FileExistsError variable static PyTypeObject _PyExc_FileExistsError -Objects/exceptions.c - PyExc_FileExistsError variable static PyTypeObject PyExc_FileExistsError -Objects/exceptions.c - _PyExc_FileNotFoundError variable static PyTypeObject _PyExc_FileNotFoundError -Objects/exceptions.c - PyExc_FileNotFoundError variable static PyTypeObject PyExc_FileNotFoundError -Objects/exceptions.c - _PyExc_FloatingPointError variable static PyTypeObject _PyExc_FloatingPointError -Objects/exceptions.c - PyExc_FloatingPointError variable static PyTypeObject PyExc_FloatingPointError -Objects/exceptions.c - _PyExc_FutureWarning variable static PyTypeObject _PyExc_FutureWarning -Objects/exceptions.c - PyExc_FutureWarning variable static PyTypeObject PyExc_FutureWarning -Objects/exceptions.c - _PyExc_GeneratorExit variable static PyTypeObject _PyExc_GeneratorExit -Objects/exceptions.c - PyExc_GeneratorExit variable static PyTypeObject PyExc_GeneratorExit -Objects/exceptions.c - _PyExc_ImportError variable static PyTypeObject _PyExc_ImportError -Objects/exceptions.c - PyExc_ImportError variable static PyTypeObject PyExc_ImportError -Objects/exceptions.c - _PyExc_ImportWarning variable static PyTypeObject _PyExc_ImportWarning -Objects/exceptions.c - PyExc_ImportWarning variable static PyTypeObject PyExc_ImportWarning -Objects/exceptions.c - _PyExc_IndentationError variable static PyTypeObject _PyExc_IndentationError -Objects/exceptions.c - PyExc_IndentationError variable static PyTypeObject PyExc_IndentationError -Objects/exceptions.c - _PyExc_IndexError variable static PyTypeObject _PyExc_IndexError -Objects/exceptions.c - PyExc_IndexError variable static PyTypeObject PyExc_IndexError -Objects/exceptions.c - _PyExc_InterruptedError variable static PyTypeObject _PyExc_InterruptedError -Objects/exceptions.c - PyExc_InterruptedError variable static PyTypeObject PyExc_InterruptedError -Objects/exceptions.c - PyExc_IOError variable static PyTypeObject PyExc_IOError -Objects/exceptions.c - _PyExc_IsADirectoryError variable static PyTypeObject _PyExc_IsADirectoryError -Objects/exceptions.c - PyExc_IsADirectoryError variable static PyTypeObject PyExc_IsADirectoryError -Objects/exceptions.c - _PyExc_KeyboardInterrupt variable static PyTypeObject _PyExc_KeyboardInterrupt -Objects/exceptions.c - PyExc_KeyboardInterrupt variable static PyTypeObject PyExc_KeyboardInterrupt -Objects/exceptions.c - _PyExc_KeyError variable static PyTypeObject _PyExc_KeyError -Objects/exceptions.c - PyExc_KeyError variable static PyTypeObject PyExc_KeyError -Objects/exceptions.c - _PyExc_LookupError variable static PyTypeObject _PyExc_LookupError -Objects/exceptions.c - PyExc_LookupError variable static PyTypeObject PyExc_LookupError -Objects/exceptions.c - _PyExc_MemoryError variable static PyTypeObject _PyExc_MemoryError -Objects/exceptions.c - PyExc_MemoryError variable static PyTypeObject PyExc_MemoryError -Objects/exceptions.c - _PyExc_ModuleNotFoundError variable static PyTypeObject _PyExc_ModuleNotFoundError -Objects/exceptions.c - PyExc_ModuleNotFoundError variable static PyTypeObject PyExc_ModuleNotFoundError -Objects/exceptions.c - _PyExc_NameError variable static PyTypeObject _PyExc_NameError -Objects/exceptions.c - PyExc_NameError variable static PyTypeObject PyExc_NameError -Objects/exceptions.c - _PyExc_NotADirectoryError variable static PyTypeObject _PyExc_NotADirectoryError -Objects/exceptions.c - PyExc_NotADirectoryError variable static PyTypeObject PyExc_NotADirectoryError -Objects/exceptions.c - _PyExc_NotImplementedError variable static PyTypeObject _PyExc_NotImplementedError -Objects/exceptions.c - PyExc_NotImplementedError variable static PyTypeObject PyExc_NotImplementedError -Objects/exceptions.c - _PyExc_OSError variable static PyTypeObject _PyExc_OSError -Objects/exceptions.c - PyExc_OSError variable static PyTypeObject PyExc_OSError -Objects/exceptions.c - _PyExc_OverflowError variable static PyTypeObject _PyExc_OverflowError -Objects/exceptions.c - PyExc_OverflowError variable static PyTypeObject PyExc_OverflowError -Objects/exceptions.c - _PyExc_PendingDeprecationWarning variable static PyTypeObject _PyExc_PendingDeprecationWarning -Objects/exceptions.c - PyExc_PendingDeprecationWarning variable static PyTypeObject PyExc_PendingDeprecationWarning -Objects/exceptions.c - _PyExc_PermissionError variable static PyTypeObject _PyExc_PermissionError -Objects/exceptions.c - PyExc_PermissionError variable static PyTypeObject PyExc_PermissionError -Objects/exceptions.c - _PyExc_ProcessLookupError variable static PyTypeObject _PyExc_ProcessLookupError -Objects/exceptions.c - PyExc_ProcessLookupError variable static PyTypeObject PyExc_ProcessLookupError -Objects/exceptions.c - _PyExc_RecursionError variable static PyTypeObject _PyExc_RecursionError -Objects/exceptions.c - PyExc_RecursionError variable static PyTypeObject PyExc_RecursionError -Objects/exceptions.c - _PyExc_ReferenceError variable static PyTypeObject _PyExc_ReferenceError -Objects/exceptions.c - PyExc_ReferenceError variable static PyTypeObject PyExc_ReferenceError -Objects/exceptions.c - _PyExc_ResourceWarning variable static PyTypeObject _PyExc_ResourceWarning -Objects/exceptions.c - PyExc_ResourceWarning variable static PyTypeObject PyExc_ResourceWarning -Objects/exceptions.c - _PyExc_RuntimeError variable static PyTypeObject _PyExc_RuntimeError -Objects/exceptions.c - PyExc_RuntimeError variable static PyTypeObject PyExc_RuntimeError -Objects/exceptions.c - _PyExc_RuntimeWarning variable static PyTypeObject _PyExc_RuntimeWarning -Objects/exceptions.c - PyExc_RuntimeWarning variable static PyTypeObject PyExc_RuntimeWarning -Objects/exceptions.c - _PyExc_StopAsyncIteration variable static PyTypeObject _PyExc_StopAsyncIteration -Objects/exceptions.c - PyExc_StopAsyncIteration variable static PyTypeObject PyExc_StopAsyncIteration -Objects/exceptions.c - _PyExc_StopIteration variable static PyTypeObject _PyExc_StopIteration -Objects/exceptions.c - PyExc_StopIteration variable static PyTypeObject PyExc_StopIteration -Objects/exceptions.c - _PyExc_SyntaxError variable static PyTypeObject _PyExc_SyntaxError -Objects/exceptions.c - PyExc_SyntaxError variable static PyTypeObject PyExc_SyntaxError -Objects/exceptions.c - _PyExc_SyntaxWarning variable static PyTypeObject _PyExc_SyntaxWarning -Objects/exceptions.c - PyExc_SyntaxWarning variable static PyTypeObject PyExc_SyntaxWarning -Objects/exceptions.c - _PyExc_SystemError variable static PyTypeObject _PyExc_SystemError -Objects/exceptions.c - PyExc_SystemError variable static PyTypeObject PyExc_SystemError -Objects/exceptions.c - _PyExc_SystemExit variable static PyTypeObject _PyExc_SystemExit -Objects/exceptions.c - PyExc_SystemExit variable static PyTypeObject PyExc_SystemExit -Objects/exceptions.c - _PyExc_TabError variable static PyTypeObject _PyExc_TabError -Objects/exceptions.c - PyExc_TabError variable static PyTypeObject PyExc_TabError -Objects/exceptions.c - _PyExc_TargetScopeError variable static PyTypeObject _PyExc_TargetScopeError -Objects/exceptions.c - PyExc_TargetScopeError variable static PyTypeObject PyExc_TargetScopeError -Objects/exceptions.c - _PyExc_TimeoutError variable static PyTypeObject _PyExc_TimeoutError -Objects/exceptions.c - PyExc_TimeoutError variable static PyTypeObject PyExc_TimeoutError -Objects/exceptions.c - _PyExc_TypeError variable static PyTypeObject _PyExc_TypeError -Objects/exceptions.c - PyExc_TypeError variable static PyTypeObject PyExc_TypeError -Objects/exceptions.c - _PyExc_UnboundLocalError variable static PyTypeObject _PyExc_UnboundLocalError -Objects/exceptions.c - PyExc_UnboundLocalError variable static PyTypeObject PyExc_UnboundLocalError -Objects/exceptions.c - _PyExc_UnicodeDecodeError variable static PyTypeObject _PyExc_UnicodeDecodeError -Objects/exceptions.c - PyExc_UnicodeDecodeError variable static PyTypeObject PyExc_UnicodeDecodeError -Objects/exceptions.c - _PyExc_UnicodeEncodeError variable static PyTypeObject _PyExc_UnicodeEncodeError -Objects/exceptions.c - PyExc_UnicodeEncodeError variable static PyTypeObject PyExc_UnicodeEncodeError -Objects/exceptions.c - _PyExc_UnicodeError variable static PyTypeObject _PyExc_UnicodeError -Objects/exceptions.c - PyExc_UnicodeError variable static PyTypeObject PyExc_UnicodeError -Objects/exceptions.c - _PyExc_UnicodeTranslateError variable static PyTypeObject _PyExc_UnicodeTranslateError -Objects/exceptions.c - PyExc_UnicodeTranslateError variable static PyTypeObject PyExc_UnicodeTranslateError -Objects/exceptions.c - _PyExc_UnicodeWarning variable static PyTypeObject _PyExc_UnicodeWarning -Objects/exceptions.c - PyExc_UnicodeWarning variable static PyTypeObject PyExc_UnicodeWarning -Objects/exceptions.c - _PyExc_UserWarning variable static PyTypeObject _PyExc_UserWarning -Objects/exceptions.c - PyExc_UserWarning variable static PyTypeObject PyExc_UserWarning -Objects/exceptions.c - _PyExc_ValueError variable static PyTypeObject _PyExc_ValueError -Objects/exceptions.c - PyExc_ValueError variable static PyTypeObject PyExc_ValueError -Objects/exceptions.c - _PyExc_Warning variable static PyTypeObject _PyExc_Warning -Objects/exceptions.c - PyExc_Warning variable static PyTypeObject PyExc_Warning -Objects/exceptions.c - _PyExc_ZeroDivisionError variable static PyTypeObject _PyExc_ZeroDivisionError -Objects/exceptions.c - PyExc_ZeroDivisionError variable static PyTypeObject PyExc_ZeroDivisionError -Objects/boolobject.c - _Py_FalseStruct variable static struct _longobject _Py_FalseStruct -Objects/tupleobject.c - _Py_fast_tuple_allocs variable Py_ssize_t _Py_fast_tuple_allocs -Objects/stringlib/unicode_format.h - PyFieldNameIter_Type variable static PyTypeObject PyFieldNameIter_Type -Modules/_io/fileio.c - PyFileIO_Type variable PyTypeObject PyFileIO_Type -Python/preconfig.c - Py_FileSystemDefaultEncodeErrors variable const char *Py_FileSystemDefaultEncodeErrors -Python/preconfig.c - Py_FileSystemDefaultEncoding variable const char * Py_FileSystemDefaultEncoding -Python/bltinmodule.c - PyFilter_Type variable PyTypeObject PyFilter_Type -Objects/floatobject.c - PyFloat_Type variable PyTypeObject PyFloat_Type -Objects/stringlib/unicode_format.h - PyFormatterIter_Type variable static PyTypeObject PyFormatterIter_Type -Objects/frameobject.c - PyFrame_Type variable PyTypeObject PyFrame_Type -Python/initconfig.c - Py_FrozenFlag variable int Py_FrozenFlag -Objects/setobject.c - PyFrozenSet_Type variable PyTypeObject PyFrozenSet_Type -Objects/funcobject.c - PyFunction_Type variable PyTypeObject PyFunction_Type -Objects/genobject.c - PyGen_Type variable PyTypeObject PyGen_Type -Objects/descrobject.c - PyGetSetDescr_Type variable PyTypeObject PyGetSetDescr_Type -Python/hamt.c - _PyHamt_ArrayNode_Type variable PyTypeObject _PyHamt_ArrayNode_Type -Python/hamt.c - PyHamt_as_mapping variable static PyMappingMethods PyHamt_as_mapping -Python/hamt.c - PyHamt_as_sequence variable static PySequenceMethods PyHamt_as_sequence -Python/hamt.c - _PyHamt_BitmapNode_Type variable PyTypeObject _PyHamt_BitmapNode_Type -Python/hamt.c - _PyHamt_CollisionNode_Type variable PyTypeObject _PyHamt_CollisionNode_Type -Python/hamt.c - _PyHamtItems_Type variable PyTypeObject _PyHamtItems_Type -Python/hamt.c - PyHamtIterator_as_mapping variable static PyMappingMethods PyHamtIterator_as_mapping -Python/hamt.c - _PyHamtKeys_Type variable PyTypeObject _PyHamtKeys_Type -Python/hamt.c - PyHamt_methods variable static PyMethodDef PyHamt_methods -Python/hamt.c - _PyHamt_Type variable PyTypeObject _PyHamt_Type -Python/hamt.c - _PyHamtValues_Type variable PyTypeObject _PyHamtValues_Type -Python/preconfig.c - _Py_HasFileSystemDefaultEncodeErrors variable const(int) _Py_HasFileSystemDefaultEncodeErrors -Python/preconfig.c - Py_HasFileSystemDefaultEncoding variable const(int) Py_HasFileSystemDefaultEncoding -Python/pyhash.c - PyHash_Func variable static PyHash_FuncDef PyHash_Func -Python/initconfig.c - Py_HashRandomizationFlag variable int Py_HashRandomizationFlag -Python/pyhash.c - _Py_HashSecret variable _Py_HashSecret_t _Py_HashSecret -Python/bootstrap_hash.c - _Py_HashSecret_Initialized variable static int _Py_HashSecret_Initialized -Python/codecs.c - Py_hexdigits variable const char * Py_hexdigits -Python/sysmodule.c - PyId__ variable _Py_IDENTIFIER(_) -Modules/_abc.c - PyId__abc_impl variable _Py_IDENTIFIER(_abc_impl) -Objects/typeobject.c - PyId___abstractmethods__ variable _Py_IDENTIFIER(__abstractmethods__) -Modules/_abc.c - PyId___abstractmethods__ variable _Py_IDENTIFIER(__abstractmethods__) -Python/ceval.c _PyEval_EvalFrameDefault PyId___aenter__ variable _Py_IDENTIFIER(__aenter__) -Python/ceval.c _PyEval_EvalFrameDefault PyId___aexit__ variable _Py_IDENTIFIER(__aexit__) -Objects/typeobject.c slot_am_aiter PyId___aiter__ variable _Py_IDENTIFIER(__aiter__) -Python/ceval.c import_all_from PyId___all__ variable _Py_IDENTIFIER(__all__) -Objects/typeobject.c slot_am_anext PyId___anext__ variable _Py_IDENTIFIER(__anext__) -Python/Python-ast.c - PyId_annotation variable _Py_IDENTIFIER(annotation) -Python/ceval.c _PyEval_EvalFrameDefault PyId___annotations__ variable _Py_IDENTIFIER(__annotations__) -Python/Python-ast.c - PyId_arg variable _Py_IDENTIFIER(arg) -Python/Python-ast.c - PyId_args variable _Py_IDENTIFIER(args) -Python/Python-ast.c - PyId_argtypes variable _Py_IDENTIFIER(argtypes) -Python/Python-ast.c - PyId_asname variable _Py_IDENTIFIER(asname) -Python/Python-ast.c make_type PyId__ast variable _Py_IDENTIFIER(_ast) -Python/Python-ast.c - PyId_attr variable _Py_IDENTIFIER(attr) -Python/Python-ast.c - PyId__attributes variable _Py_IDENTIFIER(_attributes) -Objects/typeobject.c slot_am_await PyId___await__ variable _Py_IDENTIFIER(__await__) -Python/Python-ast.c - PyId_bases variable _Py_IDENTIFIER(bases) -Modules/_abc.c - PyId___bases__ variable _Py_IDENTIFIER(__bases__) -Objects/abstract.c abstract_get_bases PyId___bases__ variable _Py_IDENTIFIER(__bases__) -Objects/typeobject.c merge_class_dict PyId___bases__ variable _Py_IDENTIFIER(__bases__) -Objects/longobject.c - PyId_big variable _Py_IDENTIFIER(big) -Modules/_io/_iomodule.c _io_open_impl PyId__blksize variable _Py_IDENTIFIER(_blksize) -Python/Python-ast.c - PyId_body variable _Py_IDENTIFIER(body) -Objects/typeobject.c slot_nb_bool PyId___bool__ variable _Py_IDENTIFIER(__bool__) -Python/sysmodule.c - PyId_buffer variable _Py_IDENTIFIER(buffer) -Python/ceval.c _PyEval_EvalFrameDefault PyId___build_class__ variable _Py_IDENTIFIER(__build_class__) -Objects/typeobject.c - PyId_builtins variable _Py_IDENTIFIER(builtins) -Python/errors.c - PyId_builtins variable _Py_IDENTIFIER(builtins) -Python/pythonrun.c - PyId_builtins variable _Py_IDENTIFIER(builtins) -Python/sysmodule.c - PyId_builtins variable _Py_IDENTIFIER(builtins) -Objects/frameobject.c - PyId___builtins__ variable _Py_IDENTIFIER(__builtins__) -Python/bltinmodule.c - PyId___builtins__ variable _Py_IDENTIFIER(__builtins__) -Python/import.c module_dict_for_exec PyId___builtins__ variable _Py_IDENTIFIER(__builtins__) -Objects/object.c - PyId___bytes__ variable _Py_IDENTIFIER(__bytes__) -Objects/bytesobject.c format_obj PyId___bytes__ variable _Py_IDENTIFIER(__bytes__) -Objects/bytesobject.c bytes_new PyId___bytes__ variable _Py_IDENTIFIER(__bytes__) -Objects/weakrefobject.c proxy_bytes PyId___bytes__ variable _Py_IDENTIFIER(__bytes__) -Objects/typeobject.c slot_tp_call PyId___call__ variable _Py_IDENTIFIER(__call__) -Python/Python-ast.c - PyId_cause variable _Py_IDENTIFIER(cause) -Objects/typeobject.c - PyId___class__ variable _Py_IDENTIFIER(__class__) -Modules/_abc.c - PyId___class__ variable _Py_IDENTIFIER(__class__) -Python/compile.c compiler_enter_scope PyId___class__ variable _Py_IDENTIFIER(__class__) -Objects/abstract.c recursive_isinstance PyId___class__ variable _Py_IDENTIFIER(__class__) -Objects/typeobject.c type_new PyId___classcell__ variable _Py_IDENTIFIER(__classcell__) -Objects/typeobject.c - PyId___class_getitem__ variable _Py_IDENTIFIER(__class_getitem__) -Objects/abstract.c PyObject_GetItem PyId___class_getitem__ variable _Py_IDENTIFIER(__class_getitem__) -Python/import.c PyImport_Cleanup PyId_clear variable _Py_IDENTIFIER(clear) -Python/traceback.c - PyId_close variable _Py_IDENTIFIER(close) -Modules/_io/bufferedio.c - PyId_close variable _Py_IDENTIFIER(close) -Modules/_io/textio.c - PyId_close variable _Py_IDENTIFIER(close) -Objects/genobject.c gen_close_iter PyId_close variable _Py_IDENTIFIER(close) -Modules/_dbmmodule.c dbm__exit__ PyId_close variable _Py_IDENTIFIER(close) -Modules/_gdbmmodule.c dbm__exit__ PyId_close variable _Py_IDENTIFIER(close) -Python/pythonrun.c _Py_HandleSystemExit PyId_code variable _Py_IDENTIFIER(code) -Python/Python-ast.c - PyId_col_offset variable _Py_IDENTIFIER(col_offset) -Python/Python-ast.c - PyId_comparators variable _Py_IDENTIFIER(comparators) -Objects/complexobject.c try_complex_special_method PyId___complex__ variable _Py_IDENTIFIER(__complex__) -Objects/typeobject.c slot_sq_contains PyId___contains__ variable _Py_IDENTIFIER(__contains__) -Python/Python-ast.c - PyId_context_expr variable _Py_IDENTIFIER(context_expr) -Python/Python-ast.c - PyId_conversion variable _Py_IDENTIFIER(conversion) -Modules/itertoolsmodule.c itertools_tee_impl PyId___copy__ variable _Py_IDENTIFIER(__copy__) -Objects/descrobject.c mappingproxy_copy PyId_copy variable _Py_IDENTIFIER(copy) -Objects/typeobject.c import_copyreg PyId_copyreg variable _Py_IDENTIFIER(copyreg) -Python/Python-ast.c - PyId_ctx variable _Py_IDENTIFIER(ctx) -Modules/_io/bufferedio.c - PyId__dealloc_warn variable _Py_IDENTIFIER(_dealloc_warn) -Modules/_io/textio.c - PyId__dealloc_warn variable _Py_IDENTIFIER(_dealloc_warn) -Modules/_io/textio.c - PyId_decode variable _Py_IDENTIFIER(decode) -Python/Python-ast.c - PyId_decorator_list variable _Py_IDENTIFIER(decorator_list) -Python/_warnings.c get_default_action PyId_defaultaction variable _Py_IDENTIFIER(defaultaction) -Python/Python-ast.c - PyId_defaults variable _Py_IDENTIFIER(defaults) -Objects/typeobject.c slot_tp_finalize PyId___del__ variable _Py_IDENTIFIER(__del__) -Objects/typeobject.c slot_tp_setattro PyId___delattr__ variable _Py_IDENTIFIER(__delattr__) -Objects/typeobject.c slot_tp_descr_set PyId___delete__ variable _Py_IDENTIFIER(__delete__) -Objects/typeobject.c - PyId___delitem__ variable _Py_IDENTIFIER(__delitem__) -Objects/typeobject.c - PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Modules/_abc.c - PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Python/bltinmodule.c - PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Python/Python-ast.c ast_type_reduce PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Python/ceval.c import_all_from PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Objects/bytearrayobject.c _common_reduce PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Objects/moduleobject.c module_dir PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Objects/odictobject.c odict_reduce PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Objects/setobject.c set_reduce PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Modules/_collectionsmodule.c deque_reduce PyId___dict__ variable _Py_IDENTIFIER(__dict__) -Objects/dictobject.c dictviews_sub PyId_difference_update variable _Py_IDENTIFIER(difference_update) -Python/Python-ast.c - PyId_dims variable _Py_IDENTIFIER(dims) -Objects/object.c - PyId___dir__ variable _Py_IDENTIFIER(__dir__) -Objects/moduleobject.c module_dir PyId___dir__ variable _Py_IDENTIFIER(__dir__) -Python/ceval.c _PyEval_EvalFrameDefault PyId_displayhook variable _Py_IDENTIFIER(displayhook) -Objects/typeobject.c - PyId___doc__ variable _Py_IDENTIFIER(__doc__) -Objects/descrobject.c property_init_impl PyId___doc__ variable _Py_IDENTIFIER(__doc__) -Objects/moduleobject.c module_init_dict PyId___doc__ variable _Py_IDENTIFIER(__doc__) -Objects/moduleobject.c PyModule_SetDocString PyId___doc__ variable _Py_IDENTIFIER(__doc__) -Python/Python-ast.c - PyId_elt variable _Py_IDENTIFIER(elt) -Python/Python-ast.c - PyId_elts variable _Py_IDENTIFIER(elts) -Modules/faulthandler.c - PyId_enable variable _Py_IDENTIFIER(enable) -Python/sysmodule.c - PyId_encoding variable _Py_IDENTIFIER(encoding) -Python/bltinmodule.c - PyId_encoding variable _Py_IDENTIFIER(encoding) -Python/pythonrun.c PyRun_InteractiveOneObjectEx PyId_encoding variable _Py_IDENTIFIER(encoding) -Python/Python-ast.c - PyId_end_col_offset variable _Py_IDENTIFIER(end_col_offset) -Python/Python-ast.c - PyId_end_lineno variable _Py_IDENTIFIER(end_lineno) -Python/ceval.c _PyEval_EvalFrameDefault PyId___enter__ variable _Py_IDENTIFIER(__enter__) -Objects/typeobject.c overrides_hash PyId___eq__ variable _Py_IDENTIFIER(__eq__) -Python/bltinmodule.c - PyId_errors variable _Py_IDENTIFIER(errors) -Python/Python-ast.c - PyId_exc variable _Py_IDENTIFIER(exc) -Python/pythonrun.c - PyId_excepthook variable _Py_IDENTIFIER(excepthook) -Python/ceval.c _PyEval_EvalFrameDefault PyId___exit__ variable _Py_IDENTIFIER(__exit__) -Modules/_pickle.c do_append PyId_extend variable _Py_IDENTIFIER(extend) -Python/Python-ast.c - PyId__fields variable _Py_IDENTIFIER(_fields) -Objects/moduleobject.c PyModule_GetFilenameObject PyId___file__ variable _Py_IDENTIFIER(__file__) -Python/errors.c PyErr_SyntaxLocationObject PyId_filename variable _Py_IDENTIFIER(filename) -Python/pythonrun.c parse_syntax_error PyId_filename variable _Py_IDENTIFIER(filename) -Modules/_io/textio.c - PyId_fileno variable _Py_IDENTIFIER(fileno) -Modules/faulthandler.c - PyId_fileno variable _Py_IDENTIFIER(fileno) -Python/bltinmodule.c - PyId_fileno variable _Py_IDENTIFIER(fileno) -Objects/fileobject.c PyObject_AsFileDescriptor PyId_fileno variable _Py_IDENTIFIER(fileno) -Modules/itertoolsmodule.c zip_longest_new PyId_fillvalue variable _Py_IDENTIFIER(fillvalue) -Python/_warnings.c get_filter PyId_filters variable _Py_IDENTIFIER(filters) -Python/Python-ast.c - PyId_finalbody variable _Py_IDENTIFIER(finalbody) -Modules/_io/iobase.c iobase_finalize PyId__finalizing variable _Py_IDENTIFIER(_finalizing) -Python/import.c import_find_and_load PyId__find_and_load variable _Py_IDENTIFIER(_find_and_load) -Python/import.c PyImport_ExecCodeModuleObject PyId__fix_up_module variable _Py_IDENTIFIER(_fix_up_module) -Python/errors.c - PyId_flush variable _Py_IDENTIFIER(flush) -Python/pylifecycle.c - PyId_flush variable _Py_IDENTIFIER(flush) -Python/pythonrun.c - PyId_flush variable _Py_IDENTIFIER(flush) -Modules/_threadmodule.c - PyId_flush variable _Py_IDENTIFIER(flush) -Modules/_io/bufferedio.c - PyId_flush variable _Py_IDENTIFIER(flush) -Modules/_io/textio.c - PyId_flush variable _Py_IDENTIFIER(flush) -Modules/faulthandler.c - PyId_flush variable _Py_IDENTIFIER(flush) -Python/bltinmodule.c - PyId_flush variable _Py_IDENTIFIER(flush) -Objects/abstract.c PyObject_Format PyId___format__ variable _Py_IDENTIFIER(__format__) -Python/Python-ast.c - PyId_format_spec variable _Py_IDENTIFIER(format_spec) -Modules/posixmodule.c path_converter PyId___fspath__ variable _Py_IDENTIFIER(__fspath__) -Modules/posixmodule.c PyOS_FSPath PyId___fspath__ variable _Py_IDENTIFIER(__fspath__) -Python/Python-ast.c - PyId_func variable _Py_IDENTIFIER(func) -Python/Python-ast.c - PyId_generators variable _Py_IDENTIFIER(generators) -Objects/descrobject.c mappingproxy_get PyId_get variable _Py_IDENTIFIER(get) -Modules/_collectionsmodule.c _count_elements PyId_get variable _Py_IDENTIFIER(get) -Objects/typeobject.c slot_tp_descr_get PyId___get__ variable _Py_IDENTIFIER(__get__) -Objects/classobject.c method_reduce PyId_getattr variable _Py_IDENTIFIER(getattr) -Objects/descrobject.c descr_reduce PyId_getattr variable _Py_IDENTIFIER(getattr) -Objects/descrobject.c wrapper_reduce PyId_getattr variable _Py_IDENTIFIER(getattr) -Objects/moduleobject.c module_getattro PyId___getattr__ variable _Py_IDENTIFIER(__getattr__) -Objects/methodobject.c meth_reduce PyId_getattr variable _Py_IDENTIFIER(getattr) -Objects/typeobject.c slot_tp_getattr_hook PyId___getattr__ variable _Py_IDENTIFIER(__getattr__) -Objects/typeobject.c - PyId___getattribute__ variable _Py_IDENTIFIER(__getattribute__) -Objects/typeobject.c - PyId___getitem__ variable _Py_IDENTIFIER(__getitem__) -Objects/typeobject.c _PyObject_GetNewArguments PyId___getnewargs__ variable _Py_IDENTIFIER(__getnewargs__) -Objects/typeobject.c _PyObject_GetNewArguments PyId___getnewargs_ex__ variable _Py_IDENTIFIER(__getnewargs_ex__) -Modules/_io/textio.c - PyId_getpreferredencoding variable _Py_IDENTIFIER(getpreferredencoding) -Python/_warnings.c get_source_line PyId_get_source variable _Py_IDENTIFIER(get_source) -Python/import.c PyImport_ExecCodeModuleWithPathnames PyId__get_sourcefile variable _Py_IDENTIFIER(_get_sourcefile) -Objects/typeobject.c _PyObject_GetState PyId___getstate__ variable _Py_IDENTIFIER(__getstate__) -Python/import.c PyImport_ImportModuleLevelObject PyId__handle_fromlist variable _Py_IDENTIFIER(_handle_fromlist) -Python/Python-ast.c - PyId_handlers variable _Py_IDENTIFIER(handlers) -Objects/typeobject.c - PyId___hash__ variable _Py_IDENTIFIER(__hash__) -Python/Python-ast.c - PyId_id variable _Py_IDENTIFIER(id) -Python/Python-ast.c - PyId_ifs variable _Py_IDENTIFIER(ifs) -Python/import.c PyImport_ReloadModule PyId_imp variable _Py_IDENTIFIER(imp) -Python/ceval.c import_name PyId___import__ variable _Py_IDENTIFIER(__import__) -Objects/typeobject.c slot_nb_index PyId___index__ variable _Py_IDENTIFIER(__index__) -Objects/typeobject.c slot_tp_init PyId___init__ variable _Py_IDENTIFIER(__init__) -Objects/moduleobject.c _PyModuleSpec_IsInitializing PyId__initializing variable _Py_IDENTIFIER(_initializing) -Objects/typeobject.c - PyId___init_subclass__ variable _Py_IDENTIFIER(__init_subclass__) -Objects/abstract.c PyObject_IsInstance PyId___instancecheck__ variable _Py_IDENTIFIER(__instancecheck__) -Objects/dictobject.c _PyDictView_Intersect PyId_intersection_update variable _Py_IDENTIFIER(intersection_update) -Modules/_io/iobase.c - PyId___IOBase_closed variable _Py_IDENTIFIER(__IOBase_closed) -Objects/typeobject.c slot_nb_inplace_power PyId___ipow__ variable _Py_IDENTIFIER(__ipow__) -Objects/object.c - PyId___isabstractmethod__ variable _Py_IDENTIFIER(__isabstractmethod__) -Python/Python-ast.c - PyId_is_async variable _Py_IDENTIFIER(is_async) -Modules/_io/bufferedio.c - PyId_isatty variable _Py_IDENTIFIER(isatty) -Modules/_io/textio.c - PyId_isatty variable _Py_IDENTIFIER(isatty) -Python/pylifecycle.c create_stdio PyId_isatty variable _Py_IDENTIFIER(isatty) -Modules/_io/_iomodule.c _io_open_impl PyId_isatty variable _Py_IDENTIFIER(isatty) -Python/codecs.c _PyCodec_LookupTextEncoding PyId__is_text_encoding variable _Py_IDENTIFIER(_is_text_encoding) -Python/Python-ast.c - PyId_items variable _Py_IDENTIFIER(items) -Objects/abstract.c PyMapping_Items PyId_items variable _Py_IDENTIFIER(items) -Objects/descrobject.c mappingproxy_items PyId_items variable _Py_IDENTIFIER(items) -Objects/odictobject.c odict_reduce PyId_items variable _Py_IDENTIFIER(items) -Objects/odictobject.c odict_repr PyId_items variable _Py_IDENTIFIER(items) -Objects/odictobject.c mutablemapping_update PyId_items variable _Py_IDENTIFIER(items) -Objects/typeobject.c _PyObject_GetItemsIter PyId_items variable _Py_IDENTIFIER(items) -Modules/_collectionsmodule.c defdict_reduce PyId_items variable _Py_IDENTIFIER(items) -Python/Python-ast.c - PyId_iter variable _Py_IDENTIFIER(iter) -Objects/bytearrayobject.c bytearrayiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/bytesobject.c striter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/dictobject.c dictiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/iterobject.c iter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/iterobject.c calliter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/listobject.c listiter_reduce_general PyId_iter variable _Py_IDENTIFIER(iter) -Objects/odictobject.c odictiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/rangeobject.c rangeiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/rangeobject.c longrangeiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/setobject.c setiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/tupleobject.c tupleiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/unicodeobject.c unicodeiter_reduce PyId_iter variable _Py_IDENTIFIER(iter) -Objects/typeobject.c slot_tp_iter PyId___iter__ variable _Py_IDENTIFIER(__iter__) -Modules/arraymodule.c array_arrayiterator___reduce___impl PyId_iter variable _Py_IDENTIFIER(iter) -Python/Python-ast.c - PyId_key variable _Py_IDENTIFIER(key) -Python/Python-ast.c - PyId_keys variable _Py_IDENTIFIER(keys) -Objects/abstract.c PyMapping_Keys PyId_keys variable _Py_IDENTIFIER(keys) -Objects/descrobject.c mappingproxy_keys PyId_keys variable _Py_IDENTIFIER(keys) -Objects/dictobject.c dict_update_common PyId_keys variable _Py_IDENTIFIER(keys) -Objects/odictobject.c mutablemapping_update PyId_keys variable _Py_IDENTIFIER(keys) -Python/Python-ast.c - PyId_keywords variable _Py_IDENTIFIER(keywords) -Python/Python-ast.c - PyId_kind variable _Py_IDENTIFIER(kind) -Python/Python-ast.c - PyId_kwarg variable _Py_IDENTIFIER(kwarg) -Python/Python-ast.c - PyId_kw_defaults variable _Py_IDENTIFIER(kw_defaults) -Python/Python-ast.c - PyId_kwonlyargs variable _Py_IDENTIFIER(kwonlyargs) -Python/pythonrun.c - PyId_last_traceback variable _Py_IDENTIFIER(last_traceback) -Python/pythonrun.c - PyId_last_type variable _Py_IDENTIFIER(last_type) -Python/pythonrun.c - PyId_last_value variable _Py_IDENTIFIER(last_value) -Python/Python-ast.c - PyId_left variable _Py_IDENTIFIER(left) -Objects/typeobject.c - PyId___len__ variable _Py_IDENTIFIER(__len__) -Objects/abstract.c PyObject_LengthHint PyId___length_hint__ variable _Py_IDENTIFIER(__length_hint__) -Python/Python-ast.c - PyId_level variable _Py_IDENTIFIER(level) -Python/Python-ast.c - PyId_lineno variable _Py_IDENTIFIER(lineno) -Python/errors.c PyErr_SyntaxLocationObject PyId_lineno variable _Py_IDENTIFIER(lineno) -Python/pythonrun.c parse_syntax_error PyId_lineno variable _Py_IDENTIFIER(lineno) -Objects/longobject.c - PyId_little variable _Py_IDENTIFIER(little) -Python/_warnings.c get_source_line PyId___loader__ variable _Py_IDENTIFIER(__loader__) -Objects/moduleobject.c module_init_dict PyId___loader__ variable _Py_IDENTIFIER(__loader__) -Python/import.c PyImport_ImportModuleLevelObject PyId__lock_unlock_module variable _Py_IDENTIFIER(_lock_unlock_module) -Python/Python-ast.c - PyId_lower variable _Py_IDENTIFIER(lower) -Python/ceval.c _PyEval_EvalFrameDefault PyId___ltrace__ variable _Py_IDENTIFIER(__ltrace__) -Python/pythonrun.c PyRun_InteractiveOneObjectEx PyId___main__ variable _Py_IDENTIFIER(__main__) -Python/_warnings.c check_matched PyId_match variable _Py_IDENTIFIER(match) -Python/bltinmodule.c - PyId_metaclass variable _Py_IDENTIFIER(metaclass) -Objects/dictobject.c dict_subscript PyId___missing__ variable _Py_IDENTIFIER(__missing__) -Modules/_io/bufferedio.c - PyId_mode variable _Py_IDENTIFIER(mode) -Modules/_io/textio.c - PyId_mode variable _Py_IDENTIFIER(mode) -Python/pylifecycle.c create_stdio PyId_mode variable _Py_IDENTIFIER(mode) -Modules/_io/_iomodule.c _io_open_impl PyId_mode variable _Py_IDENTIFIER(mode) -Python/Python-ast.c - PyId_module variable _Py_IDENTIFIER(module) -Objects/typeobject.c - PyId___module__ variable _Py_IDENTIFIER(__module__) -Python/Python-ast.c make_type PyId___module__ variable _Py_IDENTIFIER(__module__) -Python/errors.c PyErr_NewException PyId___module__ variable _Py_IDENTIFIER(__module__) -Python/errors.c PyErr_NewException PyId___module__ variable _Py_IDENTIFIER(__module__) -Python/pythonrun.c print_exception PyId___module__ variable _Py_IDENTIFIER(__module__) -Modules/_pickle.c whichmodule PyId___module__ variable _Py_IDENTIFIER(__module__) -Objects/typeobject.c type_mro_modified PyId_mro variable _Py_IDENTIFIER(mro) -Objects/typeobject.c mro_invoke PyId_mro variable _Py_IDENTIFIER(mro) -Python/bltinmodule.c - PyId___mro_entries__ variable _Py_IDENTIFIER(__mro_entries__) -Objects/typeobject.c type_new PyId___mro_entries__ variable _Py_IDENTIFIER(__mro_entries__) -Python/Python-ast.c - PyId_msg variable _Py_IDENTIFIER(msg) -Python/errors.c PyErr_SyntaxLocationObject PyId_msg variable _Py_IDENTIFIER(msg) -Python/pythonrun.c parse_syntax_error PyId_msg variable _Py_IDENTIFIER(msg) -Python/pylifecycle.c - PyId_name variable _Py_IDENTIFIER(name) -Modules/_io/fileio.c - PyId_name variable _Py_IDENTIFIER(name) -Modules/_io/bufferedio.c - PyId_name variable _Py_IDENTIFIER(name) -Modules/_io/textio.c - PyId_name variable _Py_IDENTIFIER(name) -Python/Python-ast.c - PyId_name variable _Py_IDENTIFIER(name) -Objects/exceptions.c ImportError_getstate PyId_name variable _Py_IDENTIFIER(name) -Objects/typeobject.c - PyId___name__ variable _Py_IDENTIFIER(__name__) -Objects/classobject.c - PyId___name__ variable _Py_IDENTIFIER(__name__) -Python/_warnings.c setup_context PyId___name__ variable _Py_IDENTIFIER(__name__) -Python/_warnings.c get_source_line PyId___name__ variable _Py_IDENTIFIER(__name__) -Python/_warnings.c show_warning PyId___name__ variable _Py_IDENTIFIER(__name__) -Python/ceval.c import_from PyId___name__ variable _Py_IDENTIFIER(__name__) -Python/ceval.c import_all_from PyId___name__ variable _Py_IDENTIFIER(__name__) -Python/import.c resolve_name PyId___name__ variable _Py_IDENTIFIER(__name__) -Objects/moduleobject.c module_init_dict PyId___name__ variable _Py_IDENTIFIER(__name__) -Objects/moduleobject.c PyModule_GetNameObject PyId___name__ variable _Py_IDENTIFIER(__name__) -Objects/moduleobject.c module_getattro PyId___name__ variable _Py_IDENTIFIER(__name__) -Objects/weakrefobject.c weakref_repr PyId___name__ variable _Py_IDENTIFIER(__name__) -Modules/_pickle.c save_global PyId___name__ variable _Py_IDENTIFIER(__name__) -Modules/_pickle.c save_reduce PyId___name__ variable _Py_IDENTIFIER(__name__) -Python/Python-ast.c - PyId_names variable _Py_IDENTIFIER(names) -Objects/typeobject.c - PyId___new__ variable _Py_IDENTIFIER(__new__) -Objects/typeobject.c reduce_newobj PyId___newobj__ variable _Py_IDENTIFIER(__newobj__) -Objects/typeobject.c reduce_newobj PyId___newobj_ex__ variable _Py_IDENTIFIER(__newobj_ex__) -Objects/typeobject.c slot_tp_iternext PyId___next__ variable _Py_IDENTIFIER(__next__) -Objects/structseq.c - PyId_n_fields variable _Py_IDENTIFIER(n_fields) -Python/ast.c new_identifier PyId_NFKC variable _Py_IDENTIFIER(NFKC) -Objects/structseq.c - PyId_n_sequence_fields variable _Py_IDENTIFIER(n_sequence_fields) -Objects/structseq.c - PyId_n_unnamed_fields variable _Py_IDENTIFIER(n_unnamed_fields) -Python/errors.c PyErr_SyntaxLocationObject PyId_offset variable _Py_IDENTIFIER(offset) -Python/pythonrun.c parse_syntax_error PyId_offset variable _Py_IDENTIFIER(offset) -Python/_warnings.c get_once_registry PyId_onceregistry variable _Py_IDENTIFIER(onceregistry) -Python/Python-ast.c - PyId_op variable _Py_IDENTIFIER(op) -Python/traceback.c - PyId_open variable _Py_IDENTIFIER(open) -Python/pylifecycle.c create_stdio PyId_open variable _Py_IDENTIFIER(open) -Parser/tokenizer.c fp_setreadl PyId_open variable _Py_IDENTIFIER(open) -Objects/fileobject.c PyFile_FromFd PyId_open variable _Py_IDENTIFIER(open) -Objects/fileobject.c PyFile_OpenCodeObject PyId_open variable _Py_IDENTIFIER(open) -Python/Python-ast.c - PyId_operand variable _Py_IDENTIFIER(operand) -Python/Python-ast.c - PyId_ops variable _Py_IDENTIFIER(ops) -Python/Python-ast.c - PyId_optional_vars variable _Py_IDENTIFIER(optional_vars) -Python/Python-ast.c - PyId_orelse variable _Py_IDENTIFIER(orelse) -Python/import.c resolve_name PyId___package__ variable _Py_IDENTIFIER(__package__) -Objects/moduleobject.c module_init_dict PyId___package__ variable _Py_IDENTIFIER(__package__) -Python/import.c resolve_name PyId_parent variable _Py_IDENTIFIER(parent) -Modules/_operator.c methodcaller_reduce PyId_partial variable _Py_IDENTIFIER(partial) -Python/sysmodule.c - PyId_path variable _Py_IDENTIFIER(path) -Python/traceback.c - PyId_path variable _Py_IDENTIFIER(path) -Objects/exceptions.c ImportError_getstate PyId_path variable _Py_IDENTIFIER(path) -Modules/main.c pymain_sys_path_add_path0 PyId_path variable _Py_IDENTIFIER(path) -Python/import.c resolve_name PyId___path__ variable _Py_IDENTIFIER(__path__) -Python/import.c PyImport_ImportModuleLevelObject PyId___path__ variable _Py_IDENTIFIER(__path__) -Modules/_io/bufferedio.c - PyId_peek variable _Py_IDENTIFIER(peek) -Python/Python-ast.c - PyId_posonlyargs variable _Py_IDENTIFIER(posonlyargs) -Objects/typeobject.c slot_nb_power PyId___pow__ variable _Py_IDENTIFIER(__pow__) -Python/bltinmodule.c - PyId___prepare__ variable _Py_IDENTIFIER(__prepare__) -Python/errors.c PyErr_SyntaxLocationObject PyId_print_file_and_line variable _Py_IDENTIFIER(print_file_and_line) -Python/pythonrun.c print_exception PyId_print_file_and_line variable _Py_IDENTIFIER(print_file_and_line) -Python/pythonrun.c - PyId_ps1 variable _Py_IDENTIFIER(ps1) -Python/pythonrun.c - PyId_ps2 variable _Py_IDENTIFIER(ps2) -Objects/object.c - PyId_Py_Repr variable _Py_IDENTIFIER(Py_Repr) -Objects/classobject.c - PyId___qualname__ variable _Py_IDENTIFIER(__qualname__) -Objects/descrobject.c calculate_qualname PyId___qualname__ variable _Py_IDENTIFIER(__qualname__) -Objects/methodobject.c meth_get__qualname__ PyId___qualname__ variable _Py_IDENTIFIER(__qualname__) -Objects/typeobject.c type_new PyId___qualname__ variable _Py_IDENTIFIER(__qualname__) -Modules/_io/textio.c - PyId_raw variable _Py_IDENTIFIER(raw) -Python/pylifecycle.c create_stdio PyId_raw variable _Py_IDENTIFIER(raw) -Modules/_io/iobase.c - PyId_read variable _Py_IDENTIFIER(read) -Modules/_io/bufferedio.c - PyId_read variable _Py_IDENTIFIER(read) -Modules/_io/textio.c - PyId_read variable _Py_IDENTIFIER(read) -Modules/_io/bufferedio.c - PyId_read1 variable _Py_IDENTIFIER(read1) -Python/marshal.c marshal_load PyId_read variable _Py_IDENTIFIER(read) -Modules/_io/bufferedio.c - PyId_readable variable _Py_IDENTIFIER(readable) -Modules/_io/textio.c - PyId_readable variable _Py_IDENTIFIER(readable) -Modules/_io/iobase.c _io__RawIOBase_read_impl PyId_readall variable _Py_IDENTIFIER(readall) -Modules/_io/bufferedio.c - PyId_readinto variable _Py_IDENTIFIER(readinto) -Modules/_io/bufferedio.c - PyId_readinto1 variable _Py_IDENTIFIER(readinto1) -Python/marshal.c r_string PyId_readinto variable _Py_IDENTIFIER(readinto) -Parser/tokenizer.c fp_setreadl PyId_readline variable _Py_IDENTIFIER(readline) -Objects/fileobject.c PyFile_GetLine PyId_readline variable _Py_IDENTIFIER(readline) -Objects/typeobject.c object___reduce_ex___impl PyId___reduce__ variable _Py_IDENTIFIER(__reduce__) -Python/import.c PyImport_ReloadModule PyId_reload variable _Py_IDENTIFIER(reload) -Modules/_io/textio.c - PyId_replace variable _Py_IDENTIFIER(replace) -Python/importdl.c get_encoded_name PyId_replace variable _Py_IDENTIFIER(replace) -Objects/typeobject.c slot_tp_repr PyId___repr__ variable _Py_IDENTIFIER(__repr__) -Modules/_io/textio.c - PyId_reset variable _Py_IDENTIFIER(reset) -Python/Python-ast.c - PyId_returns variable _Py_IDENTIFIER(returns) -Objects/enumobject.c reversed_new_impl PyId___reversed__ variable _Py_IDENTIFIER(__reversed__) -Objects/listobject.c listiter_reduce_general PyId_reversed variable _Py_IDENTIFIER(reversed) -Python/Python-ast.c - PyId_right variable _Py_IDENTIFIER(right) -Python/bltinmodule.c - PyId___round__ variable _Py_IDENTIFIER(__round__) -Modules/_io/textio.c - PyId_seek variable _Py_IDENTIFIER(seek) -Modules/_io/iobase.c _io__IOBase_tell_impl PyId_seek variable _Py_IDENTIFIER(seek) -Modules/_io/textio.c - PyId_seekable variable _Py_IDENTIFIER(seekable) -Python/ceval.c _PyEval_EvalFrameDefault PyId_send variable _Py_IDENTIFIER(send) -Objects/typeobject.c slot_tp_descr_set PyId___set__ variable _Py_IDENTIFIER(__set__) -Objects/typeobject.c slot_tp_setattro PyId___setattr__ variable _Py_IDENTIFIER(__setattr__) -Objects/typeobject.c - PyId___setitem__ variable _Py_IDENTIFIER(__setitem__) -Modules/_collectionsmodule.c _count_elements PyId___setitem__ variable _Py_IDENTIFIER(__setitem__) -Objects/typeobject.c - PyId___set_name__ variable _Py_IDENTIFIER(__set_name__) -Modules/_io/textio.c - PyId_setstate variable _Py_IDENTIFIER(setstate) -Modules/_pickle.c load_build PyId___setstate__ variable _Py_IDENTIFIER(__setstate__) -Python/_warnings.c call_show_warning PyId__showwarnmsg variable _Py_IDENTIFIER(_showwarnmsg) -Python/pylifecycle.c wait_for_thread_shutdown PyId__shutdown variable _Py_IDENTIFIER(_shutdown) -Python/Python-ast.c - PyId_simple variable _Py_IDENTIFIER(simple) -Python/sysmodule.c - PyId___sizeof__ variable _Py_IDENTIFIER(__sizeof__) -Python/Python-ast.c - PyId_slice variable _Py_IDENTIFIER(slice) -Objects/typeobject.c _PyType_GetSlotNames PyId___slotnames__ variable _Py_IDENTIFIER(__slotnames__) -Objects/typeobject.c _PyType_GetSlotNames PyId__slotnames variable _Py_IDENTIFIER(_slotnames) -Objects/typeobject.c type_new PyId___slots__ variable _Py_IDENTIFIER(__slots__) -Python/bltinmodule.c - PyId_sort variable _Py_IDENTIFIER(sort) -Python/import.c resolve_name PyId___spec__ variable _Py_IDENTIFIER(__spec__) -Python/import.c PyImport_ImportModuleLevelObject PyId___spec__ variable _Py_IDENTIFIER(__spec__) -Objects/moduleobject.c module_init_dict PyId___spec__ variable _Py_IDENTIFIER(__spec__) -Objects/moduleobject.c module_getattro PyId___spec__ variable _Py_IDENTIFIER(__spec__) -Python/_warnings.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Python/errors.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Python/pylifecycle.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Python/pythonrun.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Python/sysmodule.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Modules/_threadmodule.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Modules/faulthandler.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Python/bltinmodule.c - PyId_stderr variable _Py_IDENTIFIER(stderr) -Python/pylifecycle.c - PyId_stdin variable _Py_IDENTIFIER(stdin) -Python/pythonrun.c - PyId_stdin variable _Py_IDENTIFIER(stdin) -Python/bltinmodule.c - PyId_stdin variable _Py_IDENTIFIER(stdin) -Python/pylifecycle.c - PyId_stdout variable _Py_IDENTIFIER(stdout) -Python/pythonrun.c - PyId_stdout variable _Py_IDENTIFIER(stdout) -Python/sysmodule.c - PyId_stdout variable _Py_IDENTIFIER(stdout) -Python/bltinmodule.c - PyId_stdout variable _Py_IDENTIFIER(stdout) -Python/Python-ast.c - PyId_step variable _Py_IDENTIFIER(step) -Modules/posixmodule.c DirEntry_test_mode PyId_st_mode variable _Py_IDENTIFIER(st_mode) -Modules/_io/textio.c - PyId_strict variable _Py_IDENTIFIER(strict) -Python/pythonrun.c - PyId_string variable _Py_static_string(PyId_string, ""<string>"") -Modules/timemodule.c time_strptime PyId__strptime_time variable _Py_IDENTIFIER(_strptime_time) -Modules/posixmodule.c wait_helper PyId_struct_rusage variable _Py_IDENTIFIER(struct_rusage) -Modules/_abc.c - PyId___subclasscheck__ variable _Py_IDENTIFIER(__subclasscheck__) -Objects/abstract.c PyObject_IsSubclass PyId___subclasscheck__ variable _Py_IDENTIFIER(__subclasscheck__) -Modules/_abc.c - PyId___subclasshook__ variable _Py_IDENTIFIER(__subclasshook__) -Objects/dictobject.c dictviews_xor PyId_symmetric_difference_update variable _Py_IDENTIFIER(symmetric_difference_update) -Python/Python-ast.c - PyId_tag variable _Py_IDENTIFIER(tag) -Python/Python-ast.c - PyId_target variable _Py_IDENTIFIER(target) -Python/Python-ast.c - PyId_targets variable _Py_IDENTIFIER(targets) -Modules/_io/textio.c - PyId_tell variable _Py_IDENTIFIER(tell) -Python/Python-ast.c - PyId_test variable _Py_IDENTIFIER(test) -Python/errors.c PyErr_SyntaxLocationObject PyId_text variable _Py_IDENTIFIER(text) -Python/pythonrun.c parse_syntax_error PyId_text variable _Py_IDENTIFIER(text) -Python/traceback.c - PyId_TextIOWrapper variable _Py_IDENTIFIER(TextIOWrapper) -Python/pylifecycle.c create_stdio PyId_TextIOWrapper variable _Py_IDENTIFIER(TextIOWrapper) -Python/pylifecycle.c - PyId_threading variable _Py_IDENTIFIER(threading) -Objects/genobject.c _gen_throw PyId_throw variable _Py_IDENTIFIER(throw) -Objects/abstract.c PyNumber_Long PyId___trunc__ variable _Py_IDENTIFIER(__trunc__) -Python/Python-ast.c - PyId_type variable _Py_IDENTIFIER(type) -Python/Python-ast.c - PyId_type_comment variable _Py_IDENTIFIER(type_comment) -Python/Python-ast.c - PyId_type_ignores variable _Py_IDENTIFIER(type_ignores) -Python/errors.c _PyErr_WriteUnraisableMsg PyId_unraisablehook variable _Py_IDENTIFIER(unraisablehook) -Objects/dictobject.c dictviews_or PyId_update variable _Py_IDENTIFIER(update) -Python/Python-ast.c - PyId_upper variable _Py_IDENTIFIER(upper) -Python/Python-ast.c - PyId_value variable _Py_IDENTIFIER(value) -Python/Python-ast.c - PyId_values variable _Py_IDENTIFIER(values) -Objects/abstract.c PyMapping_Values PyId_values variable _Py_IDENTIFIER(values) -Objects/descrobject.c mappingproxy_values PyId_values variable _Py_IDENTIFIER(values) -Python/Python-ast.c - PyId_vararg variable _Py_IDENTIFIER(vararg) -Python/_warnings.c already_warned PyId_version variable _Py_IDENTIFIER(version) -Python/_warnings.c call_show_warning PyId_WarningMessage variable _Py_IDENTIFIER(WarningMessage) -Python/_warnings.c setup_context PyId___warningregistry__ variable _Py_IDENTIFIER(__warningregistry__) -Python/_warnings.c get_warnings_attr PyId_warnings variable _Py_IDENTIFIER(warnings) -Python/sysmodule.c - PyId_warnoptions variable _Py_IDENTIFIER(warnoptions) -Python/_warnings.c _PyErr_WarnUnawaitedCoroutine PyId__warn_unawaited_coroutine variable _Py_IDENTIFIER(_warn_unawaited_coroutine) -Modules/_io/bufferedio.c - PyId_writable variable _Py_IDENTIFIER(writable) -Modules/_io/textio.c - PyId_writable variable _Py_IDENTIFIER(writable) -Python/sysmodule.c - PyId_write variable _Py_IDENTIFIER(write) -Modules/_io/bufferedio.c - PyId_write variable _Py_IDENTIFIER(write) -Python/marshal.c marshal_dump_impl PyId_write variable _Py_IDENTIFIER(write) -Objects/fileobject.c PyFile_WriteObject PyId_write variable _Py_IDENTIFIER(write) -Python/sysmodule.c - PyId__xoptions variable _Py_IDENTIFIER(_xoptions) -Python/import.c _PyImportZip_Init PyId_zipimporter variable _Py_IDENTIFIER(zipimporter) -Python/initconfig.c - Py_IgnoreEnvironmentFlag variable int Py_IgnoreEnvironmentFlag -Python/dynload_shlib.c - _PyImport_DynLoadFiletab variable const char *_PyImport_DynLoadFiletab[] -Python/frozen.c - PyImport_FrozenModules variable const struct _frozen * PyImport_FrozenModules -Modules/config.c - _PyImport_Inittab variable struct _inittab _PyImport_Inittab[] -Python/import.c - PyImport_Inittab variable struct _inittab * PyImport_Inittab -Modules/_io/textio.c - PyIncrementalNewlineDecoder_Type variable PyTypeObject PyIncrementalNewlineDecoder_Type -Python/initconfig.c - Py_InspectFlag variable int Py_InspectFlag -Objects/classobject.c - PyInstanceMethod_Type variable PyTypeObject PyInstanceMethod_Type -Python/initconfig.c - Py_InteractiveFlag variable int Py_InteractiveFlag -Objects/interpreteridobject.c - _PyInterpreterID_Type variable PyTypeObject _PyInterpreterID_Type -Modules/_io/iobase.c - PyIOBase_Type variable PyTypeObject PyIOBase_Type -Modules/_io/_iomodule.c - _PyIO_empty_bytes variable PyObject *_PyIO_empty_bytes -Modules/_io/_iomodule.c - _PyIO_empty_str variable PyObject *_PyIO_empty_str -Modules/_io/_iomodule.c - _PyIO_Module variable struct PyModuleDef _PyIO_Module -Modules/_io/_iomodule.c - _PyIO_str_close variable PyObject *_PyIO_str_close -Modules/_io/_iomodule.c - _PyIO_str_closed variable PyObject *_PyIO_str_closed -Modules/_io/_iomodule.c - _PyIO_str_decode variable PyObject *_PyIO_str_decode -Modules/_io/_iomodule.c - _PyIO_str_encode variable PyObject *_PyIO_str_encode -Modules/_io/_iomodule.c - _PyIO_str_fileno variable PyObject *_PyIO_str_fileno -Modules/_io/_iomodule.c - _PyIO_str_flush variable PyObject *_PyIO_str_flush -Modules/_io/_iomodule.c - _PyIO_str_getstate variable PyObject *_PyIO_str_getstate -Modules/_io/_iomodule.c - _PyIO_str_isatty variable PyObject *_PyIO_str_isatty -Modules/_io/_iomodule.c - _PyIO_str_newlines variable PyObject *_PyIO_str_newlines -Modules/_io/_iomodule.c - _PyIO_str_nl variable PyObject *_PyIO_str_nl -Modules/_io/_iomodule.c - _PyIO_str_peek variable PyObject *_PyIO_str_peek -Modules/_io/_iomodule.c - _PyIO_str_read variable PyObject *_PyIO_str_read -Modules/_io/_iomodule.c - _PyIO_str_read1 variable PyObject *_PyIO_str_read1 -Modules/_io/_iomodule.c - _PyIO_str_readable variable PyObject *_PyIO_str_readable -Modules/_io/_iomodule.c - _PyIO_str_readall variable PyObject *_PyIO_str_readall -Modules/_io/_iomodule.c - _PyIO_str_readinto variable PyObject *_PyIO_str_readinto -Modules/_io/_iomodule.c - _PyIO_str_readline variable PyObject *_PyIO_str_readline -Modules/_io/_iomodule.c - _PyIO_str_reset variable PyObject *_PyIO_str_reset -Modules/_io/_iomodule.c - _PyIO_str_seek variable PyObject *_PyIO_str_seek -Modules/_io/_iomodule.c - _PyIO_str_seekable variable PyObject *_PyIO_str_seekable -Modules/_io/_iomodule.c - _PyIO_str_setstate variable PyObject *_PyIO_str_setstate -Modules/_io/_iomodule.c - _PyIO_str_tell variable PyObject *_PyIO_str_tell -Modules/_io/_iomodule.c - _PyIO_str_truncate variable PyObject *_PyIO_str_truncate -Modules/_io/_iomodule.c - _PyIO_str_writable variable PyObject *_PyIO_str_writable -Modules/_io/_iomodule.c - _PyIO_str_write variable PyObject *_PyIO_str_write -Python/initconfig.c - Py_IsolatedFlag variable int Py_IsolatedFlag -Objects/listobject.c - PyListIter_Type variable PyTypeObject PyListIter_Type -Objects/listobject.c - PyListRevIter_Type variable PyTypeObject PyListRevIter_Type -Objects/listobject.c - PyList_Type variable PyTypeObject PyList_Type -Modules/_localemodule.c - PyLocale_Methods variable static struct PyMethodDef PyLocale_Methods[] -Objects/longobject.c - _PyLong_DigitValue variable unsigned char _PyLong_DigitValue[256] -Objects/longobject.c - _PyLong_One variable PyObject *_PyLong_One -Objects/rangeobject.c - PyLongRangeIter_Type variable PyTypeObject PyLongRangeIter_Type -Objects/longobject.c - PyLong_Type variable PyTypeObject PyLong_Type -Objects/longobject.c - _PyLong_Zero variable PyObject *_PyLong_Zero -Objects/memoryobject.c - _PyManagedBuffer_Type variable PyTypeObject _PyManagedBuffer_Type -Python/bltinmodule.c - PyMap_Type variable PyTypeObject PyMap_Type -Objects/obmalloc.c - _PyMem variable static PyMemAllocatorEx _PyMem -Objects/descrobject.c - PyMemberDescr_Type variable PyTypeObject PyMemberDescr_Type -Objects/obmalloc.c - _PyMem_Debug variable static struct { debug_alloc_api_t raw; debug_alloc_api_t mem; debug_alloc_api_t obj; } _PyMem_Debug -Objects/memoryobject.c - PyMemoryView_Type variable PyTypeObject PyMemoryView_Type -Objects/obmalloc.c - _PyMem_Raw variable static PyMemAllocatorEx _PyMem_Raw -Objects/descrobject.c - PyMethodDescr_Type variable PyTypeObject PyMethodDescr_Type -Objects/classobject.c - PyMethod_Type variable PyTypeObject PyMethod_Type -Objects/descrobject.c - _PyMethodWrapper_Type variable PyTypeObject _PyMethodWrapper_Type -Objects/moduleobject.c - PyModuleDef_Type variable PyTypeObject PyModuleDef_Type -Objects/moduleobject.c - PyModule_Type variable PyTypeObject PyModule_Type -Objects/namespaceobject.c - _PyNamespace_Type variable PyTypeObject _PyNamespace_Type -Objects/object.c - _Py_NoneStruct variable PyObject _Py_NoneStruct -Objects/object.c - _PyNone_Type variable PyTypeObject _PyNone_Type -Python/initconfig.c - Py_NoSiteFlag variable int Py_NoSiteFlag -Objects/object.c - _Py_NotImplementedStruct variable PyObject _Py_NotImplementedStruct -Objects/object.c - _PyNotImplemented_Type variable PyTypeObject _PyNotImplemented_Type -Python/initconfig.c - Py_NoUserSiteDirectory variable int Py_NoUserSiteDirectory -Objects/bytesobject.c - _Py_null_strings variable Py_ssize_t _Py_null_strings -Objects/obmalloc.c - _PyObject variable static PyMemAllocatorEx _PyObject -Objects/obmalloc.c - _PyObject_Arena variable static PyObjectArenaAllocator _PyObject_Arena -Objects/odictobject.c - PyODictItems_Type variable PyTypeObject PyODictItems_Type -Objects/odictobject.c - PyODictIter_Type variable PyTypeObject PyODictIter_Type -Objects/odictobject.c - PyODictKeys_Type variable PyTypeObject PyODictKeys_Type -Objects/odictobject.c - PyODict_Type variable PyTypeObject PyODict_Type -Objects/odictobject.c - PyODictValues_Type variable PyTypeObject PyODictValues_Type -Python/fileutils.c - _Py_open_cloexec_works variable int _Py_open_cloexec_works -Objects/bytesobject.c - _Py_one_strings variable Py_ssize_t _Py_one_strings -Python/initconfig.c - Py_OptimizeFlag variable int Py_OptimizeFlag -Parser/myreadline.c - PyOS_InputHook variable int (*PyOS_InputHook)(void) -Python/pylifecycle.c - _PyOS_mystrnicmp_hack variable int (*_PyOS_mystrnicmp_hack)(const char *, const char *, Py_ssize_t) -Python/getopt.c - _PyOS_optarg variable const wchar_t *_PyOS_optarg -Python/getopt.c - _PyOS_opterr variable int _PyOS_opterr -Python/getopt.c - _PyOS_optind variable Py_ssize_t _PyOS_optind -Parser/myreadline.c - PyOS_ReadlineFunctionPointer variable char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, const char *) -Parser/myreadline.c - _PyOS_ReadlineLock variable static PyThread_type_lock _PyOS_ReadlineLock -Parser/myreadline.c - _PyOS_ReadlineTState variable PyThreadState* _PyOS_ReadlineTState -Python/modsupport.c - _Py_PackageContext variable const char *_Py_PackageContext -Python/graminit.c - _PyParser_Grammar variable grammar _PyParser_Grammar -Python/pathconfig.c - _Py_path_config variable _PyPathConfig _Py_path_config -Objects/picklebufobject.c - PyPickleBuffer_Type variable PyTypeObject PyPickleBuffer_Type -Objects/descrobject.c - PyProperty_Type variable PyTypeObject PyProperty_Type -Python/initconfig.c - Py_QuietFlag variable int Py_QuietFlag -Objects.longobject.c - _Py_quick_int_allocs variable Py_ssize_t _Py_quick_int_allocs -Objects.longobject.c - _Py_quick_new_int_allocs variable Py_ssize_t _Py_quick_new_int_allocs -Objects/rangeobject.c - PyRangeIter_Type variable PyTypeObject PyRangeIter_Type -Objects/rangeobject.c - PyRange_Type variable PyTypeObject PyRange_Type -Modules/_io/iobase.c - PyRawIOBase_Type variable PyTypeObject PyRawIOBase_Type -Objects/object.c - _Py_RefTotal variable Py_ssize_t _Py_RefTotal -Objects/enumobject.c - PyReversed_Type variable PyTypeObject PyReversed_Type -Python/pylifecycle.c - _PyRuntime variable _PyRuntimeState _PyRuntime -Objects/iterobject.c - PySeqIter_Type variable PyTypeObject PySeqIter_Type -Objects/setobject.c - _PySet_Dummy variable PyObject * _PySet_Dummy -Objects/setobject.c - _PySetDummy_Type variable static PyTypeObject _PySetDummy_Type -Objects/setobject.c - PySetIter_Type variable PyTypeObject PySetIter_Type -Objects/setobject.c - PySet_Type variable PyTypeObject PySet_Type -Objects/sliceobject.c - PySlice_Type variable PyTypeObject PySlice_Type -Python/initconfig.c - _Py_StandardStreamEncoding variable static char *_Py_StandardStreamEncoding -Python/initconfig.c - _Py_StandardStreamErrors variable static char *_Py_StandardStreamErrors -Objects/funcobject.c - PyStaticMethod_Type variable PyTypeObject PyStaticMethod_Type -Objects/fileobject.c - PyStdPrinter_Type variable PyTypeObject PyStdPrinter_Type -Python/symtable.c - PySTEntry_Type variable PyTypeObject PySTEntry_Type -Modules/_io/stringio.c - PyStringIO_Type variable PyTypeObject PyStringIO_Type -Objects/structseq.c - PyStructSequence_UnnamedField variable char *PyStructSequence_UnnamedField -Objects/typeobject.c - PySuper_Type variable PyTypeObject PySuper_Type -Objects/object.c - _Py_SwappedOp variable int _Py_SwappedOp[] -Python/sysmodule.c - _PySys_ImplCacheTag variable const char *_PySys_ImplCacheTag -Python/sysmodule.c - _PySys_ImplName variable const char *_PySys_ImplName -Modules/_io/textio.c - PyTextIOBase_Type variable PyTypeObject PyTextIOBase_Type -Modules/_io/textio.c - PyTextIOWrapper_Type variable PyTypeObject PyTextIOWrapper_Type -Python/traceback.c - PyTraceBack_Type variable PyTypeObject PyTraceBack_Type -Objects/obmalloc.c - _Py_tracemalloc_config variable struct _PyTraceMalloc_Config _Py_tracemalloc_config -Objects/boolobject.c - _Py_TrueStruct variable static struct _longobject _Py_TrueStruct -Objects/tupleobject.c - PyTupleIter_Type variable PyTypeObject PyTupleIter_Type -Objects/tupleobject.c - PyTuple_Type variable PyTypeObject PyTuple_Type -Objects/tupleobject.c - _Py_tuple_zero_allocs variable Py_ssize_t _Py_tuple_zero_allocs -Objects/typeobject.c - PyType_Type variable PyTypeObject PyType_Type -Python/initconfig.c - Py_UnbufferedStdioFlag variable int Py_UnbufferedStdioFlag -Python/pylifecycle.c - _Py_UnhandledKeyboardInterrupt variable int _Py_UnhandledKeyboardInterrupt -Objects/unicodeobject.c - PyUnicodeIter_Type variable PyTypeObject PyUnicodeIter_Type -Objects/unicodeobject.c - PyUnicode_Type variable PyTypeObject PyUnicode_Type -Python/initconfig.c - Py_UTF8Mode variable int Py_UTF8Mode -Python/initconfig.c - Py_VerboseFlag variable int Py_VerboseFlag -Objects/weakrefobject.c - _PyWeakref_CallableProxyType variable PyTypeObject _PyWeakref_CallableProxyType -Objects/weakrefobject.c - _PyWeakref_ProxyType variable PyTypeObject _PyWeakref_ProxyType -Objects/weakrefobject.c - _PyWeakref_RefType variable PyTypeObject _PyWeakref_RefType -Objects/weakrefobject.c - _PyWeakref_RefType variable PyTypeObject _PyWeakref_RefType -Objects/descrobject.c - PyWrapperDescr_Type variable PyTypeObject PyWrapperDescr_Type -Python/bltinmodule.c - PyZip_Type variable PyTypeObject PyZip_Type -Python/Python-ast.c - Raise_fields variable static const char *Raise_fields[] -Python/Python-ast.c - Raise_type variable static PyTypeObject *Raise_type -Objects/rangeobject.c - range_as_mapping variable static PyMappingMethods range_as_mapping -Objects/rangeobject.c - range_as_number variable static PyNumberMethods range_as_number -Objects/rangeobject.c - range_as_sequence variable static PySequenceMethods range_as_sequence -Objects/rangeobject.c - rangeiter_methods variable static PyMethodDef rangeiter_methods -Objects/rangeobject.c - range_members variable static PyMemberDef range_members[] -Objects/rangeobject.c - range_methods variable static PyMethodDef range_methods -Modules/_io/iobase.c - rawiobase_methods variable static PyMethodDef rawiobase_methods -Python/pylifecycle.c fatal_error reentrant variable static int reentrant -Modules/faulthandler.c faulthandler_dump_traceback reentrant variable static volatile int reentrant -Modules/itertoolsmodule.c - repeat_methods variable static PyMethodDef repeat_methods -Modules/itertoolsmodule.c - repeat_type variable static PyTypeObject repeat_type -Python/Python-ast.c - Return_fields variable static const char *Return_fields[] -Python/compile.c compiler_visit_annotations return_str variable static identifier return_str -Python/Python-ast.c - Return_type variable static PyTypeObject *Return_type -Objects/enumobject.c - reversediter_methods variable static PyMethodDef reversediter_methods -Modules/_threadmodule.c - rlock_methods variable static PyMethodDef rlock_methods -Modules/_threadmodule.c - RLocktype variable static PyTypeObject RLocktype -Objects/typeobject.c slot_nb_add rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_subtract rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_multiply rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_matrix_multiply rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_remainder rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_divmod rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_power_binary rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_lshift rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_rshift rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_and rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_xor rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_or rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_floor_divide rop_id variable _Py_static_string(op_id, OPSTR) -Objects/typeobject.c slot_nb_true_divide rop_id variable _Py_static_string(op_id, OPSTR) -Python/Python-ast.c - RShift_singleton variable static PyObject *RShift_singleton -Python/Python-ast.c - RShift_type variable static PyTypeObject *RShift_type -Python/pylifecycle.c - runtime_initialized variable static int runtime_initialized -Modules/posixmodule.c - ScandirIterator_methods variable static PyMethodDef ScandirIterator_methods -Modules/posixmodule.c - ScandirIteratorType variable static PyTypeObject ScandirIteratorType -Modules/_sre.c - scanner_members variable static PyMemberDef scanner_members[] -Modules/_sre.c - scanner_methods variable static PyMethodDef scanner_methods -Modules/_sre.c - Scanner_Type variable static PyTypeObject Scanner_Type -Modules/posixmodule.c - sched_param_desc variable static PyStructSequence_Desc sched_param_desc -Modules/posixmodule.c - sched_param_fields variable static PyStructSequence_Field sched_param_fields[] -Modules/posixmodule.c - SchedParamType variable static PyTypeObject* SchedParamType -Objects/iterobject.c - seqiter_methods variable static PyMethodDef seqiter_methods -Objects/setobject.c - set_as_number variable static PyNumberMethods set_as_number -Objects/setobject.c - set_as_sequence variable static PySequenceMethods set_as_sequence -Python/symtable.c - setcomp variable static identifier setcomp -Python/Python-ast.c - SetComp_fields variable static const char *SetComp_fields[] -Python/Python-ast.c - SetComp_type variable static PyTypeObject *SetComp_type -Python/Python-ast.c - Set_fields variable static const char *Set_fields[] -Objects/setobject.c - setiter_methods variable static PyMethodDef setiter_methods -Objects/setobject.c - set_methods variable static PyMethodDef set_methods -Python/Python-ast.c - Set_type variable static PyTypeObject *Set_type -Modules/signalmodule.c - SiginfoType variable static PyTypeObject SiginfoType -Modules/signalmodule.c - signal_methods variable static PyMethodDef signal_methods -Modules/signalmodule.c - signalmodule variable static struct PyModuleDef signalmodule -Python/import.c PyImport_Import silly_list variable static PyObject *silly_list -Objects/sliceobject.c - slice_cache variable static PySliceObject *slice_cache -Python/Python-ast.c - Slice_fields variable static const char *Slice_fields[] -Objects/sliceobject.c - slice_members variable static PyMemberDef slice_members[] -Objects/sliceobject.c - slice_methods variable static PyMethodDef slice_methods -Python/Python-ast.c - slice_type variable static PyTypeObject *slice_type -Python/Python-ast.c - Slice_type variable static PyTypeObject *Slice_type -Objects/typeobject.c - slotdefs variable static slotdef slotdefs[] -Objects/typeobject.c - slotdefs_initialized variable static int slotdefs_initialized -Objects/longobject.c - small_ints variable static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS] -Objects/funcobject.c - sm_getsetlist variable static PyGetSetDef sm_getsetlist[] -Objects/funcobject.c - sm_memberlist variable static PyMemberDef sm_memberlist[] -Modules/xxsubtype.c - spamdict_members variable static PyMemberDef spamdict_members[] -Modules/xxsubtype.c - spamdict_methods variable static PyMethodDef spamdict_methods -Modules/xxsubtype.c - spamdict_type variable static PyTypeObject spamdict_type -Modules/xxsubtype.c - spamlist_getsets variable static PyGetSetDef spamlist_getsets[] -Modules/xxsubtype.c - spamlist_methods variable static PyMethodDef spamlist_methods -Modules/xxsubtype.c - spamlist_type variable static PyTypeObject spamlist_type -Modules/_sre.c - sremodule variable static struct PyModuleDef sremodule -Modules/faulthandler.c - stack variable static stack_t stack -Modules/itertoolsmodule.c - starmap_methods variable static PyMethodDef starmap_methods -Modules/itertoolsmodule.c - starmap_type variable static PyTypeObject starmap_type -Python/Python-ast.c - Starred_fields variable static const char *Starred_fields[] -Python/Python-ast.c - Starred_type variable static PyTypeObject *Starred_type -Python/graminit.c - states_0 variable static state states_0[3] -Python/graminit.c - states_1 variable static state states_1[2] -Python/graminit.c - states_10 variable static state states_10[4] -Python/graminit.c - states_11 variable static state states_11[34] -Python/graminit.c - states_12 variable static state states_12[2] -Python/graminit.c - states_13 variable static state states_13[2] -Python/graminit.c - states_14 variable static state states_14[4] -Python/graminit.c - states_15 variable static state states_15[2] -Python/graminit.c - states_16 variable static state states_16[6] -Python/graminit.c - states_17 variable static state states_17[5] -Python/graminit.c - states_18 variable static state states_18[3] -Python/graminit.c - states_19 variable static state states_19[2] -Python/graminit.c - states_2 variable static state states_2[3] -Python/graminit.c - states_20 variable static state states_20[3] -Python/graminit.c - states_21 variable static state states_21[2] -Python/graminit.c - states_22 variable static state states_22[2] -Python/graminit.c - states_23 variable static state states_23[2] -Python/graminit.c - states_24 variable static state states_24[2] -Python/graminit.c - states_25 variable static state states_25[3] -Python/graminit.c - states_26 variable static state states_26[2] -Python/graminit.c - states_27 variable static state states_27[5] -Python/graminit.c - states_28 variable static state states_28[2] -Python/graminit.c - states_29 variable static state states_29[3] -Python/graminit.c - states_3 variable static state states_3[7] -Python/graminit.c - states_30 variable static state states_30[8] -Python/graminit.c - states_31 variable static state states_31[4] -Python/graminit.c - states_32 variable static state states_32[4] -Python/graminit.c - states_33 variable static state states_33[3] -Python/graminit.c - states_34 variable static state states_34[2] -Python/graminit.c - states_35 variable static state states_35[2] -Python/graminit.c - states_36 variable static state states_36[3] -Python/graminit.c - states_37 variable static state states_37[3] -Python/graminit.c - states_38 variable static state states_38[5] -Python/graminit.c - states_39 variable static state states_39[2] -Python/graminit.c - states_4 variable static state states_4[2] -Python/graminit.c - states_40 variable static state states_40[3] -Python/graminit.c - states_41 variable static state states_41[8] -Python/graminit.c - states_42 variable static state states_42[8] -Python/graminit.c - states_43 variable static state states_43[11] -Python/graminit.c - states_44 variable static state states_44[13] -Python/graminit.c - states_45 variable static state states_45[6] -Python/graminit.c - states_46 variable static state states_46[4] -Python/graminit.c - states_47 variable static state states_47[5] -Python/graminit.c - states_48 variable static state states_48[5] -Python/graminit.c - states_49 variable static state states_49[4] -Python/graminit.c - states_5 variable static state states_5[3] -Python/graminit.c - states_50 variable static state states_50[6] -Python/graminit.c - states_51 variable static state states_51[2] -Python/graminit.c - states_52 variable static state states_52[5] -Python/graminit.c - states_53 variable static state states_53[5] -Python/graminit.c - states_54 variable static state states_54[2] -Python/graminit.c - states_55 variable static state states_55[2] -Python/graminit.c - states_56 variable static state states_56[3] -Python/graminit.c - states_57 variable static state states_57[2] -Python/graminit.c - states_58 variable static state states_58[4] -Python/graminit.c - states_59 variable static state states_59[3] -Python/graminit.c - states_6 variable static state states_6[3] -Python/graminit.c - states_60 variable static state states_60[2] -Python/graminit.c - states_61 variable static state states_61[2] -Python/graminit.c - states_62 variable static state states_62[2] -Python/graminit.c - states_63 variable static state states_63[2] -Python/graminit.c - states_64 variable static state states_64[2] -Python/graminit.c - states_65 variable static state states_65[2] -Python/graminit.c - states_66 variable static state states_66[3] -Python/graminit.c - states_67 variable static state states_67[4] -Python/graminit.c - states_68 variable static state states_68[3] -Python/graminit.c - states_69 variable static state states_69[9] -Python/graminit.c - states_7 variable static state states_7[9] -Python/graminit.c - states_70 variable static state states_70[5] -Python/graminit.c - states_71 variable static state states_71[7] -Python/graminit.c - states_72 variable static state states_72[3] -Python/graminit.c - states_73 variable static state states_73[5] -Python/graminit.c - states_74 variable static state states_74[3] -Python/graminit.c - states_75 variable static state states_75[3] -Python/graminit.c - states_76 variable static state states_76[3] -Python/graminit.c - states_77 variable static state states_77[14] -Python/graminit.c - states_78 variable static state states_78[8] -Python/graminit.c - states_79 variable static state states_79[3] -Python/graminit.c - states_8 variable static state states_8[4] -Python/graminit.c - states_80 variable static state states_80[4] -Python/graminit.c - states_81 variable static state states_81[2] -Python/graminit.c - states_82 variable static state states_82[6] -Python/graminit.c - states_83 variable static state states_83[3] -Python/graminit.c - states_84 variable static state states_84[4] -Python/graminit.c - states_85 variable static state states_85[2] -Python/graminit.c - states_86 variable static state states_86[3] -Python/graminit.c - states_87 variable static state states_87[3] -Python/graminit.c - states_88 variable static state states_88[7] -Python/graminit.c - states_89 variable static state states_89[3] -Python/graminit.c - states_9 variable static state states_9[42] -Python/graminit.c - states_90 variable static state states_90[6] -Python/graminit.c - states_91 variable static state states_91[11] -Python/getargs.c - static_arg_parsers variable static struct _PyArg_Parser *static_arg_parsers -Objects/unicodeobject.c - static_strings variable static _Py_Identifier *static_strings -Modules/_stat.c - stat_methods variable static PyMethodDef stat_methods -Modules/_stat.c - statmodule variable static struct PyModuleDef statmodule -Modules/posixmodule.c - stat_result_desc variable static PyStructSequence_Desc stat_result_desc -Modules/posixmodule.c - stat_result_fields variable static PyStructSequence_Field stat_result_fields[] -Modules/posixmodule.c - StatResultType variable static PyTypeObject* StatResultType -Modules/posixmodule.c - statvfs_result_desc variable static PyStructSequence_Desc statvfs_result_desc -Modules/posixmodule.c - statvfs_result_fields variable static PyStructSequence_Field statvfs_result_fields[] -Modules/posixmodule.c - StatVFSResultType variable static PyTypeObject* StatVFSResultType -Objects/fileobject.c - stdprinter_getsetlist variable static PyGetSetDef stdprinter_getsetlist[] -Objects/fileobject.c - stdprinter_methods variable static PyMethodDef stdprinter_methods -Python/symtable.c - ste_memberlist variable static PyMemberDef ste_memberlist[] -Python/Python-ast.c - stmt_attributes variable static const char *stmt_attributes[] -Python/Python-ast.c - stmt_type variable static PyTypeObject *stmt_type -Objects/exceptions.c - StopIteration_members variable static PyMemberDef StopIteration_members[] -Python/Python-ast.c - Store_singleton variable static PyObject *Store_singleton -Python/Python-ast.c - Store_type variable static PyTypeObject *Store_type -Python/ast_unparse.c - _str_close_br variable static PyObject *_str_close_br -Python/ast_unparse.c - _str_dbl_close_br variable static PyObject *_str_dbl_close_br -Python/ast_unparse.c - _str_dbl_open_br variable static PyObject *_str_dbl_open_br -Modules/_threadmodule.c - str_dict variable static PyObject *str_dict -Modules/_io/stringio.c - stringio_getset variable static PyGetSetDef stringio_getset[] -Modules/_io/stringio.c - stringio_methods variable static PyMethodDef stringio_methods -Objects/unicodeobject.c - _string_methods variable static PyMethodDef _string_methods -Objects/unicodeobject.c - _string_module variable static struct PyModuleDef _string_module -Objects/bytesobject.c - striter_methods variable static PyMethodDef striter_methods -Python/ast_unparse.c - _str_open_br variable static PyObject *_str_open_br -Modules/pwdmodule.c - StructPwdType variable static PyTypeObject StructPwdType -Modules/pwdmodule.c - struct_pwd_type_desc variable static PyStructSequence_Desc struct_pwd_type_desc -Modules/pwdmodule.c - struct_pwd_type_fields variable static PyStructSequence_Field struct_pwd_type_fields[] -Modules/posixmodule.c wait_helper struct_rusage variable static PyObject *struct_rusage -Objects/structseq.c - structseq_methods variable static PyMethodDef structseq_methods -Modules/posixmodule.c - structseq_new variable static newfunc structseq_new -Modules/signalmodule.c - struct_siginfo_desc variable static PyStructSequence_Desc struct_siginfo_desc -Modules/signalmodule.c - struct_siginfo_fields variable static PyStructSequence_Field struct_siginfo_fields[] -Modules/timemodule.c - StructTimeType variable static PyTypeObject StructTimeType -Modules/timemodule.c - struct_time_type_desc variable static PyStructSequence_Desc struct_time_type_desc -Modules/timemodule.c - struct_time_type_fields variable static PyStructSequence_Field struct_time_type_fields[] -Python/Python-ast.c - Subscript_fields variable static const char *Subscript_fields[] -Python/Python-ast.c - Subscript_type variable static PyTypeObject *Subscript_type -Python/Python-ast.c - Sub_singleton variable static PyObject *Sub_singleton -Python/Python-ast.c - Sub_type variable static PyTypeObject *Sub_type -Objects/typeobject.c - subtype_getsets_dict_only variable static PyGetSetDef subtype_getsets_dict_only[] -Objects/typeobject.c - subtype_getsets_full variable static PyGetSetDef subtype_getsets_full[] -Objects/typeobject.c - subtype_getsets_weakref_only variable static PyGetSetDef subtype_getsets_weakref_only[] -Python/Python-ast.c - Suite_fields variable static const char *Suite_fields[] -Python/Python-ast.c - Suite_type variable static PyTypeObject *Suite_type -Objects/typeobject.c - super_members variable static PyMemberDef super_members[] -Modules/symtablemodule.c - symtable_methods variable static PyMethodDef symtable_methods -Modules/symtablemodule.c - symtablemodule variable static struct PyModuleDef symtablemodule -Objects/exceptions.c - SyntaxError_members variable static PyMemberDef SyntaxError_members[] -Python/sysmodule.c - sys_methods variable static PyMethodDef sys_methods -Python/sysmodule.c - sysmodule variable static struct PyModuleDef sysmodule -Objects/exceptions.c - SystemExit_members variable static PyMemberDef SystemExit_members[] -Modules/_tracemalloc.c - tables_lock variable static PyThread_type_lock tables_lock -Modules/itertoolsmodule.c - takewhile_reduce_methods variable static PyMethodDef takewhile_reduce_methods -Modules/itertoolsmodule.c - takewhile_type variable static PyTypeObject takewhile_type -Python/pylifecycle.c - _TARGET_LOCALES variable static _LocaleCoercionTarget _TARGET_LOCALES[] -Python/traceback.c - tb_getsetters variable static PyGetSetDef tb_getsetters[] -Python/traceback.c - tb_memberlist variable static PyMemberDef tb_memberlist[] -Python/traceback.c - tb_methods variable static PyMethodDef tb_methods -Modules/itertoolsmodule.c - teedataobject_methods variable static PyMethodDef teedataobject_methods -Modules/itertoolsmodule.c - teedataobject_type variable static PyTypeObject teedataobject_type -Modules/itertoolsmodule.c - tee_methods variable static PyMethodDef tee_methods -Modules/itertoolsmodule.c - tee_type variable static PyTypeObject tee_type -Modules/posixmodule.c - TerminalSize_desc variable static PyStructSequence_Desc TerminalSize_desc -Modules/posixmodule.c - TerminalSize_fields variable static PyStructSequence_Field TerminalSize_fields[] -Modules/posixmodule.c - TerminalSizeType variable static PyTypeObject* TerminalSizeType -Modules/_io/textio.c - textiobase_getset variable static PyGetSetDef textiobase_getset[] -Modules/_io/textio.c - textiobase_methods variable static PyMethodDef textiobase_methods -Modules/_io/textio.c - textiowrapper_getset variable static PyGetSetDef textiowrapper_getset[] -Modules/_io/textio.c - textiowrapper_members variable static PyMemberDef textiowrapper_members[] -Modules/_io/textio.c - textiowrapper_methods variable static PyMethodDef textiowrapper_methods -Modules/faulthandler.c - thread variable static struct { PyObject *file; int fd; PY_TIMEOUT_T timeout_us; int repeat; PyInterpreterState *interp; int exit; char *header; size_t header_len; PyThread_type_lock cancel_event; PyThread_type_lock running; } thread -Python/thread.c - thread_debug variable static int thread_debug -Modules/_threadmodule.c - ThreadError variable static PyObject *ThreadError -Python/thread.c - threadinfo_desc variable static PyStructSequence_Desc threadinfo_desc -Python/thread.c - threadinfo_fields variable static PyStructSequence_Field threadinfo_fields[] -Python/thread.c - ThreadInfoType variable static PyTypeObject ThreadInfoType -Modules/_threadmodule.c - thread_methods variable static PyMethodDef thread_methods -Modules/_threadmodule.c - threadmodule variable static struct PyModuleDef threadmodule -Modules/posixmodule.c - ticks_per_second variable static long ticks_per_second -Modules/timemodule.c _PyTime_GetProcessTimeWithInfo ticks_per_second variable static long ticks_per_second -Modules/timemodule.c - time_methods variable static PyMethodDef time_methods -Modules/timemodule.c - timemodule variable static struct PyModuleDef timemodule -Modules/posixmodule.c - times_result_desc variable static PyStructSequence_Desc times_result_desc -Modules/posixmodule.c - times_result_fields variable static PyStructSequence_Field times_result_fields[] -Modules/posixmodule.c - TimesResultType variable static PyTypeObject* TimesResultType -Python/context.c - _token_missing variable static PyObject *_token_missing -Python/symtable.c - top variable static identifier top -Objects/typeobject.c - tp_new_methoddef variable static struct PyMethodDef tp_new_methoddef[] -Modules/_tracemalloc.c - tracemalloc_empty_traceback variable static traceback_t tracemalloc_empty_traceback -Modules/_tracemalloc.c - tracemalloc_filenames variable static _Py_hashtable_t *tracemalloc_filenames -Modules/_tracemalloc.c - tracemalloc_peak_traced_memory variable static size_t tracemalloc_peak_traced_memory -Modules/_tracemalloc.c - tracemalloc_reentrant_key variable static Py_tss_t tracemalloc_reentrant_key -Modules/_tracemalloc.c - tracemalloc_traceback variable static traceback_t *tracemalloc_traceback -Modules/_tracemalloc.c - tracemalloc_tracebacks variable static _Py_hashtable_t *tracemalloc_tracebacks -Modules/_tracemalloc.c - tracemalloc_traced_memory variable static size_t tracemalloc_traced_memory -Modules/_tracemalloc.c - tracemalloc_traces variable static _Py_hashtable_t *tracemalloc_traces -Objects/boolobject.c - true_str variable static PyObject *true_str -Python/Python-ast.c - Try_fields variable static const char *Try_fields[] -Python/Python-ast.c - Try_type variable static PyTypeObject *Try_type -Objects/tupleobject.c - tuple_as_mapping variable static PyMappingMethods tuple_as_mapping -Objects/tupleobject.c - tuple_as_sequence variable static PySequenceMethods tuple_as_sequence -Python/Python-ast.c - Tuple_fields variable static const char *Tuple_fields[] -Modules/_collectionsmodule.c - tuplegetter_members variable static PyMemberDef tuplegetter_members[] -Modules/_collectionsmodule.c - tuplegetter_methods variable static PyMethodDef tuplegetter_methods -Modules/_collectionsmodule.c - tuplegetter_type variable static PyTypeObject tuplegetter_type -Objects/tupleobject.c - tupleiter_methods variable static PyMethodDef tupleiter_methods -Objects/tupleobject.c - tuple_methods variable static PyMethodDef tuple_methods -Python/Python-ast.c - Tuple_type variable static PyTypeObject *Tuple_type -Objects/typeobject.c - type_getsets variable static PyGetSetDef type_getsets[] -Python/Python-ast.c - TypeIgnore_fields variable static const char *TypeIgnore_fields[] -Python/Python-ast.c - type_ignore_type variable static PyTypeObject *type_ignore_type -Python/Python-ast.c - TypeIgnore_type variable static PyTypeObject *TypeIgnore_type -Objects/typeobject.c - type_members variable static PyMemberDef type_members[] -Objects/typeobject.c - type_methods variable static PyMethodDef type_methods -Python/Python-ast.c - UAdd_singleton variable static PyObject *UAdd_singleton -Python/Python-ast.c - UAdd_type variable static PyTypeObject *UAdd_type -Objects/unicodeobject.c - ucnhash_CAPI variable static _PyUnicode_Name_CAPI *ucnhash_CAPI -Python/codecs.c - ucnhash_CAPI variable static _PyUnicode_Name_CAPI *ucnhash_CAPI -Python/ast.c - u_kind variable static PyObject *u_kind -Modules/posixmodule.c - uname_result_desc variable static PyStructSequence_Desc uname_result_desc -Modules/posixmodule.c - uname_result_fields variable static PyStructSequence_Field uname_result_fields[] -Modules/posixmodule.c - UnameResultType variable static PyTypeObject* UnameResultType -Python/Python-ast.c - UnaryOp_fields variable static const char *UnaryOp_fields[] -Python/Python-ast.c - unaryop_type variable static PyTypeObject *unaryop_type -Python/Python-ast.c - UnaryOp_type variable static PyTypeObject *UnaryOp_type -Objects/unicodeobject.c - unicode_as_mapping variable static PyMappingMethods unicode_as_mapping -Objects/unicodeobject.c - unicode_as_number variable static PyNumberMethods unicode_as_number -Objects/unicodeobject.c - unicode_as_sequence variable static PySequenceMethods unicode_as_sequence -Objects/unicodeobject.c - unicode_empty variable static PyObject *unicode_empty -Objects/exceptions.c - UnicodeError_members variable static PyMemberDef UnicodeError_members[] -Objects/unicodeobject.c - unicodeiter_methods variable static PyMethodDef unicodeiter_methods -Objects/unicodeobject.c - unicode_latin1 variable static PyObject *unicode_latin1[256] -Objects/unicodeobject.c - unicode_methods variable static PyMethodDef unicode_methods -Modules/_tracemalloc.c - unknown_filename variable static PyObject *unknown_filename -Python/errors.c - UnraisableHookArgs_desc variable static PyStructSequence_Desc UnraisableHookArgs_desc -Python/errors.c - UnraisableHookArgs_fields variable static PyStructSequence_Field UnraisableHookArgs_fields[] -Python/errors.c - UnraisableHookArgsType variable static PyTypeObject UnraisableHookArgsType -Objects/obmalloc.c - unused_arena_objects variable static struct arena_object* unused_arena_objects -Python/bootstrap_hash.c - urandom_cache variable static struct { int fd; dev_t st_dev; ino_t st_ino; } urandom_cache -Objects/obmalloc.c - usable_arenas variable static struct arena_object* usable_arenas -Objects/obmalloc.c - usedpools variable static poolp usedpools[2 * ((NB_SMALL_SIZE_CLASSES + 7) / 8) * 8] -Modules/faulthandler.c - user_signals variable static user_signal_t *user_signals -Python/Python-ast.c - USub_singleton variable static PyObject *USub_singleton -Python/Python-ast.c - USub_type variable static PyTypeObject *USub_type -Python/getversion.c Py_GetVersion version variable static char version[250] -Python/sysmodule.c - version_info_desc variable static PyStructSequence_Desc version_info_desc -Python/sysmodule.c - version_info_fields variable static PyStructSequence_Field version_info_fields[] -Python/sysmodule.c - VersionInfoType variable static PyTypeObject VersionInfoType -Modules/posixmodule.c - waitid_result_desc variable static PyStructSequence_Desc waitid_result_desc -Modules/posixmodule.c - waitid_result_fields variable static PyStructSequence_Field waitid_result_fields[] -Modules/posixmodule.c - WaitidResultType variable static PyTypeObject* WaitidResultType -Modules/signalmodule.c - wakeup variable static volatile struct { SOCKET_T fd; int warn_on_full_buffer; int use_send; } wakeup -Python/_warnings.c - warnings_functions variable static PyMethodDef warnings_functions[] -Python/_warnings.c - warningsmodule variable static struct PyModuleDef warningsmodule -Modules/_weakref.c - weakref_functions variable static PyMethodDef weakref_functions -Objects/weakrefobject.c - weakref_members variable static PyMemberDef weakref_members[] -Modules/_weakref.c - weakrefmodule variable static struct PyModuleDef weakrefmodule -Python/sysmodule.c - whatstrings variable static PyObject *whatstrings[8] -Python/Python-ast.c - While_fields variable static const char *While_fields[] -Python/Python-ast.c - While_type variable static PyTypeObject *While_type -Python/Python-ast.c - With_fields variable static const char *With_fields[] -Python/Python-ast.c - withitem_fields variable static const char *withitem_fields[] -Python/Python-ast.c - withitem_type variable static PyTypeObject *withitem_type -Python/Python-ast.c - With_type variable static PyTypeObject *With_type -Objects/descrobject.c - wrapperdescr_getset variable static PyGetSetDef wrapperdescr_getset[] -Objects/descrobject.c - wrapper_getsets variable static PyGetSetDef wrapper_getsets[] -Objects/descrobject.c - wrapper_members variable static PyMemberDef wrapper_members[] -Objects/descrobject.c - wrapper_methods variable static PyMethodDef wrapper_methods -Modules/_threadmodule.c local_new wr_callback_def variable static PyMethodDef wr_callback_def -Modules/xxsubtype.c - xxsubtype_functions variable static PyMethodDef xxsubtype_functions[] -Modules/xxsubtype.c - xxsubtypemodule variable static struct PyModuleDef xxsubtypemodule -Modules/xxsubtype.c - xxsubtype_slots variable static struct PyModuleDef_Slot xxsubtype_slots[] -Python/Python-ast.c - Yield_fields variable static const char *Yield_fields[] -Python/Python-ast.c - YieldFrom_fields variable static const char *YieldFrom_fields[] -Python/Python-ast.c - YieldFrom_type variable static PyTypeObject *YieldFrom_type -Python/Python-ast.c - Yield_type variable static PyTypeObject *Yield_type -Modules/itertoolsmodule.c - zip_longest_methods variable static PyMethodDef zip_longest_methods -Modules/itertoolsmodule.c - ziplongest_type variable static PyTypeObject ziplongest_type -Python/bltinmodule.c - zip_methods variable static PyMethodDef zip_methods |