run-clang-tools.py 3.0 KB

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