mirror of
https://github.com/python/cpython.git
synced 2025-10-25 10:44:55 +00:00
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
196 lines
4.9 KiB
Python
196 lines
4.9 KiB
Python
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)
|