cpython/Tools/c-analyzer/c_globals/__main__.py

210 lines
6.1 KiB
Python

import argparse
import os.path
import re
import sys
from c_analyzer_common import SOURCE_DIRS, REPO_ROOT
from c_analyzer_common.info import UNKNOWN
from c_analyzer_common.known import (
from_file as known_from_file,
DATA_FILE as KNOWN_FILE,
)
from . import find, show
from .supported import is_supported, ignored_from_file, IGNORED_FILE, _is_object
def _match_unused_global(variable, knownvars, used):
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
def _check_results(unknown, knownvars, used):
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, knownvars, used):
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')
def _find_globals(dirnames, known, ignored):
if dirnames == SOURCE_DIRS:
dirnames = [os.path.relpath(d, REPO_ROOT) for d in dirnames]
ignored = ignored_from_file(ignored)
known = known_from_file(known)
used = set()
unknown = set()
knownvars = (known or {}).get('variables')
for variable in find.globals_from_binary(knownvars=knownvars,
dirnames=dirnames):
#for variable in find.globals(dirnames, known, kind='platform'):
if variable.vartype == UNKNOWN:
unknown.add(variable)
continue
yield variable, is_supported(variable, ignored, known)
used.add(variable.id)
#_check_results(unknown, knownvars, used)
def cmd_check(cmd, dirs=SOURCE_DIRS, *,
ignored=IGNORED_FILE,
known=KNOWN_FILE,
_find=_find_globals,
_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 = [v for v, s in _find(dirs, known, ignored) if not s]
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, dirs=SOURCE_DIRS, *,
ignored=IGNORED_FILE,
known=KNOWN_FILE,
skip_objects=False,
_find=_find_globals,
_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(dirs, known, ignored):
if skip_objects: # XXX Support proper filters instead.
if _is_object(found.vartype):
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,
)
subs = parser.add_subparsers(dest='cmd')
check = subs.add_parser('check', parents=[common])
show = subs.add_parser('show', parents=[common])
show.add_argument('--skip-objects', action='store_true')
if _fail is None:
def _fail(msg):
parser.error(msg)
# 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
def main(cmd, cmdkwargs=None, *, _COMMANDS=COMMANDS):
try:
cmdfunc = _COMMANDS[cmd]
except KeyError:
raise ValueError(
f'unsupported cmd {cmd!r}' if cmd else 'missing cmd')
cmdfunc(cmd, **cmdkwargs or {})
if __name__ == '__main__':
cmd, cmdkwargs = parse_args()
main(cmd, cmdkwargs)