clang_static_analyzer.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import subprocess
  2. import sys
  3. import os
  4. import re
  5. import argparse
  6. import json
  7. CLANG_SA_CONFIG='static_analyzer.json'
  8. def parse_args():
  9. parser = argparse.ArgumentParser()
  10. parser.add_argument("--testing-src", required=True)
  11. parser.add_argument("--clang-bin", required=True)
  12. parser.add_argument("--source-root", required=True)
  13. parser.add_argument("--config-file", required=True)
  14. parser.add_argument("--plugins-begin", dest='plugins', action='append', nargs='+', required=True)
  15. parser.add_argument("--plugins-end", action='store_true', required=True)
  16. return parser.parse_known_args()
  17. def find_config(config_path):
  18. # For unifying config files names
  19. basename = os.path.basename(config_path)
  20. if basename != CLANG_SA_CONFIG:
  21. msg = "The config file should be called {}, but {} passed".format(CLANG_SA_CONFIG, basename)
  22. raise ValueError(msg)
  23. if not os.path.isfile(config_path):
  24. raise ValueError("Cant find config file {}".format(config_path))
  25. return config_path
  26. def parse_config(config_file):
  27. conf = None
  28. try:
  29. with open(config_file, 'r') as afile:
  30. conf = json.load(afile)
  31. except:
  32. conf = None
  33. return conf
  34. def should_analyze(filename, conf):
  35. include_files = conf.get('include_files')
  36. exclude_files = conf.get('exclude_files')
  37. if not include_files:
  38. return False
  39. include = re.match(include_files, filename)
  40. exclude = re.match(exclude_files, filename) if exclude_files else False
  41. return include and not exclude
  42. def load_plugins(conf, plugins):
  43. load_cmds = []
  44. for plugin in filter(lambda path: os.path.isfile(path), plugins):
  45. load_cmds.extend(["-Xclang", "-load", "-Xclang", plugin])
  46. return load_cmds
  47. def main():
  48. args, clang_cmd = parse_args()
  49. # Try to find config file and parse them
  50. config_file = find_config(args.config_file)
  51. conf = parse_config(config_file)
  52. # Ensure we can read config
  53. if not conf:
  54. raise ValueError(f"Cant parse config file, check its syntax: {config_file}")
  55. # Ensure we have at least one check
  56. if ('checks' not in conf) or (not conf['checks']):
  57. raise ValueError("There are no checks in the config file")
  58. # Ensure that file match regex
  59. if not should_analyze(args.testing_src, conf):
  60. return 0
  61. # Prepare args
  62. analyzer_opts = [
  63. '-Wno-unused-command-line-argument',
  64. '--analyze',
  65. '--analyzer-outputtext',
  66. '--analyzer-no-default-checks'
  67. ]
  68. analyzer_opts.extend(['-Xanalyzer', '-analyzer-werror'])
  69. analyzer_opts.extend(['-Xanalyzer', '-analyzer-checker=' + ','.join(conf['checks'])])
  70. # Load additional plugins
  71. analyzer_opts.extend(load_plugins(conf, args.plugins[0]))
  72. run_cmd = [args.clang_bin, args.testing_src] + clang_cmd + analyzer_opts
  73. p = subprocess.run(run_cmd)
  74. return p.returncode
  75. if __name__ == '__main__':
  76. ret_code = 0
  77. try:
  78. ret_code = main()
  79. except Exception as e:
  80. print("\n[Error]: " + str(e), file=sys.stderr)
  81. ret_code = 1
  82. exit(ret_code)