| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: GPL-2.0 | |
| # | |
| # Copyright (C) Google LLC, 2020 | |
| # | |
| # Author: Nathan Huckleberry <nhuck@google.com> | |
| # | |
| """A helper routine run clang-tidy and the clang static-analyzer on | |
| compile_commands.json. | |
| """ | |
| import argparse | |
| import json | |
| import multiprocessing | |
| import subprocess | |
| import sys | |
| def parse_arguments(): | |
| """Set up and parses command-line arguments. | |
| Returns: | |
| args: Dict of parsed args | |
| Has keys: [path, type] | |
| """ | |
| usage = """Run clang-tidy or the clang static-analyzer on a | |
| compilation database.""" | |
| parser = argparse.ArgumentParser(description=usage) | |
| type_help = "Type of analysis to be performed" | |
| parser.add_argument("type", | |
| choices=["clang-tidy", "clang-analyzer"], | |
| help=type_help) | |
| path_help = "Path to the compilation database to parse" | |
| parser.add_argument("path", type=str, help=path_help) | |
| checks_help = "Checks to pass to the analysis" | |
| parser.add_argument("-checks", type=str, default=None, help=checks_help) | |
| header_filter_help = "Pass the -header-filter value to the tool" | |
| parser.add_argument("-header-filter", type=str, default=None, help=header_filter_help) | |
| return parser.parse_args() | |
| def init(l, a): | |
| global lock | |
| global args | |
| lock = l | |
| args = a | |
| def run_analysis(entry): | |
| # Disable all checks, then re-enable the ones we want | |
| global args | |
| checks = None | |
| if args.checks: | |
| checks = args.checks.split(',') | |
| else: | |
| checks = ["-*"] | |
| if args.type == "clang-tidy": | |
| checks.append("linuxkernel-*") | |
| else: | |
| checks.append("clang-analyzer-*") | |
| checks.append("-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling") | |
| file = entry["file"] | |
| if not file.endswith(".c") and not file.endswith(".cpp"): | |
| with lock: | |
| print(f"Skipping non-C file: '{file}'", file=sys.stderr) | |
| return | |
| pargs = ["clang-tidy", "-p", args.path, "-checks=" + ",".join(checks)] | |
| if args.header_filter: | |
| pargs.append("-header-filter=" + args.header_filter) | |
| pargs.append(file) | |
| p = subprocess.run(pargs, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| cwd=entry["directory"]) | |
| with lock: | |
| sys.stderr.buffer.write(p.stdout) | |
| def main(): | |
| args = parse_arguments() | |
| # Read JSON data into the datastore variable | |
| with open(args.path) as f: | |
| datastore = json.load(f) | |
| lock = multiprocessing.Lock() | |
| try: | |
| with multiprocessing.Pool(initializer=init, initargs=(lock, args)) as pool: | |
| pool.map(run_analysis, datastore) | |
| except BrokenPipeError: | |
| # Python flushes standard streams on exit; redirect remaining output | |
| # to devnull to avoid another BrokenPipeError at shutdown | |
| devnull = os.open(os.devnull, os.O_WRONLY) | |
| os.dup2(devnull, sys.stdout.fileno()) | |
| sys.exit(1) # Python exits with error code 1 on EPIPE | |
| if __name__ == "__main__": | |
| main() |