aboutsummaryrefslogtreecommitdiffstats
path: root/build/scripts/clang_static_analyzer.py
blob: 128d45fdad65c1bef13104ba5966bcdd926bfa22 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import subprocess
import sys
import os
import re
import argparse
import json

CLANG_SA_CONFIG='static_analyzer.json'

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--testing-src", required=True)
    parser.add_argument("--clang-bin", required=True)
    parser.add_argument("--source-root", required=True)
    parser.add_argument("--config-file", required=True)
    parser.add_argument("--plugins-begin", dest='plugins', action='append', nargs='+', required=True)
    parser.add_argument("--plugins-end", action='store_true', required=True)
    return parser.parse_known_args()

def find_config(config_path):
    # For unifying config files names
    basename = os.path.basename(config_path)
    if basename != CLANG_SA_CONFIG:
        msg = "The config file should be called {}, but {} passed".format(CLANG_SA_CONFIG, basename)
        raise ValueError(msg)
    if not os.path.isfile(config_path):
        raise ValueError("Cant find config file {}".format(config_path))
    return config_path

def parse_config(config_file):
    conf = None
    try:
        with open(config_file, 'r') as afile:
            conf = json.load(afile)
    except:
        conf = None
    return conf

def should_analyze(filename, conf):
    include_files = conf.get('include_files')
    exclude_files = conf.get('exclude_files')

    if not include_files:
        return False

    include = re.match(include_files, filename)
    exclude = re.match(exclude_files, filename) if exclude_files else False

    return include and not exclude

def load_plugins(conf, plugins):
    load_cmds = []
    for plugin in filter(lambda path: os.path.isfile(path), plugins):
        load_cmds.extend(["-Xclang", "-load", "-Xclang", plugin])
    return load_cmds

def main():
    args, clang_cmd = parse_args()

    # Try to find config file and parse them
    config_file = find_config(args.config_file)
    conf = parse_config(config_file)

    # Ensure we can read config
    if not conf:
        raise ValueError(f"Cant parse config file, check its syntax: {config_file}")

    # Ensure we have at least one check
    if ('checks' not in conf) or (not conf['checks']):
        raise ValueError("There are no checks in the config file")

    # Ensure that file match regex
    if not should_analyze(args.testing_src, conf):
        return 0

    # Prepare args
    analyzer_opts = [
        '-Wno-unused-command-line-argument',
        '--analyze',
        '--analyzer-outputtext',
        '--analyzer-no-default-checks'
    ]
    analyzer_opts.extend(['-Xanalyzer', '-analyzer-werror'])
    analyzer_opts.extend(['-Xanalyzer', '-analyzer-checker=' + ','.join(conf['checks'])])

    # Load additional plugins
    analyzer_opts.extend(load_plugins(conf, args.plugins[0]))

    run_cmd = [args.clang_bin, args.testing_src] + clang_cmd + analyzer_opts
    p = subprocess.run(run_cmd)

    return p.returncode

if __name__ == '__main__':
    ret_code = 0
    try:
        ret_code = main()
    except Exception as e:
        print("\n[Error]: " + str(e), file=sys.stderr)
        ret_code = 1
    exit(ret_code)