210 lines
6.1 KiB
Python
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)
|