clang_static_analyzer.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import subprocess
  2. import sys
  3. import os
  4. import re
  5. import argparse
  6. import yaml
  7. CLANG_SA_CONFIG='static_analyzer.yaml'
  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 = yaml.safe_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 have at least one check
  53. if ('checks' not in conf) or (not conf['checks']):
  54. raise ValueError("There are no checks in the config file")
  55. # Ensure that file match regex
  56. if not should_analyze(args.testing_src, conf):
  57. return 0
  58. # Prepare args
  59. analyzer_opts = [
  60. '-Wno-unused-command-line-argument',
  61. '--analyze',
  62. '--analyzer-outputtext',
  63. '--analyzer-no-default-checks'
  64. ]
  65. analyzer_opts.extend(['-Xanalyzer', '-analyzer-werror'])
  66. analyzer_opts.extend(['-Xanalyzer', '-analyzer-checker=' + ','.join(conf['checks'])])
  67. # Load additional plugins
  68. analyzer_opts.extend(load_plugins(conf, args.plugins[0]))
  69. run_cmd = [args.clang_bin, args.testing_src] + clang_cmd + analyzer_opts
  70. p = subprocess.run(run_cmd)
  71. return p.returncode
  72. if __name__ == '__main__':
  73. ret_code = 0
  74. try:
  75. ret_code = main()
  76. except Exception as e:
  77. print("\n[Error]: " + str(e), file=sys.stderr)
  78. ret_code = 1
  79. exit(ret_code)