1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3# 4# Copyright (C) Google LLC, 2020 5# 6# Author: Nathan Huckleberry <nhuck@google.com> 7# 8"""A helper routine run clang-tidy and the clang static-analyzer on 9compile_commands.json. 10""" 11 12import argparse 13import json 14import multiprocessing 15import os 16import subprocess 17import sys 18 19 20def parse_arguments(): 21 """Set up and parses command-line arguments. 22 Returns: 23 args: Dict of parsed args 24 Has keys: [path, type] 25 """ 26 usage = """Run clang-tidy or the clang static-analyzer on a 27 compilation database.""" 28 parser = argparse.ArgumentParser(description=usage) 29 30 type_help = "Type of analysis to be performed" 31 parser.add_argument("type", 32 choices=["clang-tidy", "clang-analyzer"], 33 help=type_help) 34 path_help = "Path to the compilation database to parse" 35 parser.add_argument("path", type=str, help=path_help) 36 37 checks_help = "Checks to pass to the analysis" 38 parser.add_argument("-checks", type=str, default=None, help=checks_help) 39 header_filter_help = "Pass the -header-filter value to the tool" 40 parser.add_argument("-header-filter", type=str, default=None, help=header_filter_help) 41 42 return parser.parse_args() 43 44 45def init(l, a): 46 global lock 47 global args 48 lock = l 49 args = a 50 51 52def run_analysis(entry): 53 # Disable all checks, then re-enable the ones we want 54 global args 55 checks = None 56 if args.checks: 57 checks = args.checks.split(',') 58 else: 59 checks = ["-*"] 60 if args.type == "clang-tidy": 61 checks.append("linuxkernel-*") 62 else: 63 checks.append("clang-analyzer-*") 64 checks.append("-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling") 65 file = entry["file"] 66 if not file.endswith(".c") and not file.endswith(".cpp"): 67 with lock: 68 print(f"Skipping non-C file: '{file}'", file=sys.stderr) 69 return 70 pargs = ["clang-tidy", "-p", args.path, "-checks=" + ",".join(checks)] 71 if args.header_filter: 72 pargs.append("-header-filter=" + args.header_filter) 73 pargs.append(file) 74 p = subprocess.run(pargs, 75 stdout=subprocess.PIPE, 76 stderr=subprocess.STDOUT, 77 cwd=entry["directory"]) 78 with lock: 79 sys.stderr.buffer.write(p.stdout) 80 81 82def main(): 83 args = parse_arguments() 84 85 # Read JSON data into the datastore variable 86 with open(args.path) as f: 87 datastore = json.load(f) 88 89 lock = multiprocessing.Lock() 90 try: 91 with multiprocessing.Pool(initializer=init, initargs=(lock, args)) as pool: 92 pool.map(run_analysis, datastore) 93 except BrokenPipeError: 94 # Python flushes standard streams on exit; redirect remaining output 95 # to devnull to avoid another BrokenPipeError at shutdown 96 devnull = os.open(os.devnull, os.O_WRONLY) 97 os.dup2(devnull, sys.stdout.fileno()) 98 sys.exit(1) # Python exits with error code 1 on EPIPE 99 100 101if __name__ == "__main__": 102 main() 103