xref: /linux/scripts/clang-tools/run-clang-tools.py (revision 5d7f3375cd574fc83f0cd30839b21455c0e79ae2)
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