Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
14 | pmbaty | 1 | # -*- coding: utf-8 -*- |
2 | # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
||
3 | # See https://llvm.org/LICENSE.txt for license information. |
||
4 | # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
||
5 | """ This module is responsible for the Clang executable. |
||
6 | |||
7 | Since Clang command line interface is so rich, but this project is using only |
||
8 | a subset of that, it makes sense to create a function specific wrapper. """ |
||
9 | |||
10 | import subprocess |
||
11 | import re |
||
12 | from libscanbuild import run_command |
||
13 | from libscanbuild.shell import decode |
||
14 | |||
15 | __all__ = ['get_version', 'get_arguments', 'get_checkers', 'is_ctu_capable', |
||
16 | 'get_triple_arch'] |
||
17 | |||
18 | # regex for activated checker |
||
19 | ACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$') |
||
20 | |||
21 | |||
22 | class ClangErrorException(Exception): |
||
23 | def __init__(self, error): |
||
24 | self.error = error |
||
25 | |||
26 | |||
27 | def get_version(clang): |
||
28 | """ Returns the compiler version as string. |
||
29 | |||
30 | :param clang: the compiler we are using |
||
31 | :return: the version string printed to stderr """ |
||
32 | |||
33 | output = run_command([clang, '-v']) |
||
34 | # the relevant version info is in the first line |
||
35 | return output[0] |
||
36 | |||
37 | |||
38 | def get_arguments(command, cwd): |
||
39 | """ Capture Clang invocation. |
||
40 | |||
41 | :param command: the compilation command |
||
42 | :param cwd: the current working directory |
||
43 | :return: the detailed front-end invocation command """ |
||
44 | |||
45 | cmd = command[:] |
||
46 | cmd.insert(1, '-###') |
||
47 | cmd.append('-fno-color-diagnostics') |
||
48 | |||
49 | output = run_command(cmd, cwd=cwd) |
||
50 | # The relevant information is in the last line of the output. |
||
51 | # Don't check if finding last line fails, would throw exception anyway. |
||
52 | last_line = output[-1] |
||
53 | if re.search(r'clang(.*): error:', last_line): |
||
54 | raise ClangErrorException(last_line) |
||
55 | return decode(last_line) |
||
56 | |||
57 | |||
58 | def get_active_checkers(clang, plugins): |
||
59 | """ Get the active checker list. |
||
60 | |||
61 | :param clang: the compiler we are using |
||
62 | :param plugins: list of plugins which was requested by the user |
||
63 | :return: list of checker names which are active |
||
64 | |||
65 | To get the default checkers we execute Clang to print how this |
||
66 | compilation would be called. And take out the enabled checker from the |
||
67 | arguments. For input file we specify stdin and pass only language |
||
68 | information. """ |
||
69 | |||
70 | def get_active_checkers_for(language): |
||
71 | """ Returns a list of active checkers for the given language. """ |
||
72 | |||
73 | load_args = [arg |
||
74 | for plugin in plugins |
||
75 | for arg in ['-Xclang', '-load', '-Xclang', plugin]] |
||
76 | cmd = [clang, '--analyze'] + load_args + ['-x', language, '-'] |
||
77 | return [ACTIVE_CHECKER_PATTERN.match(arg).group(1) |
||
78 | for arg in get_arguments(cmd, '.') |
||
79 | if ACTIVE_CHECKER_PATTERN.match(arg)] |
||
80 | |||
81 | result = set() |
||
82 | for language in ['c', 'c++', 'objective-c', 'objective-c++']: |
||
83 | result.update(get_active_checkers_for(language)) |
||
84 | return frozenset(result) |
||
85 | |||
86 | |||
87 | def is_active(checkers): |
||
88 | """ Returns a method, which classifies the checker active or not, |
||
89 | based on the received checker name list. """ |
||
90 | |||
91 | def predicate(checker): |
||
92 | """ Returns True if the given checker is active. """ |
||
93 | |||
94 | return any(pattern.match(checker) for pattern in predicate.patterns) |
||
95 | |||
96 | predicate.patterns = [re.compile(r'^' + a + r'(\.|$)') for a in checkers] |
||
97 | return predicate |
||
98 | |||
99 | |||
100 | def parse_checkers(stream): |
||
101 | """ Parse clang -analyzer-checker-help output. |
||
102 | |||
103 | Below the line 'CHECKERS:' are there the name description pairs. |
||
104 | Many of them are in one line, but some long named checker has the |
||
105 | name and the description in separate lines. |
||
106 | |||
107 | The checker name is always prefixed with two space character. The |
||
108 | name contains no whitespaces. Then followed by newline (if it's |
||
109 | too long) or other space characters comes the description of the |
||
110 | checker. The description ends with a newline character. |
||
111 | |||
112 | :param stream: list of lines to parse |
||
113 | :return: generator of tuples |
||
114 | |||
115 | (<checker name>, <checker description>) """ |
||
116 | |||
117 | lines = iter(stream) |
||
118 | # find checkers header |
||
119 | for line in lines: |
||
120 | if re.match(r'^CHECKERS:', line): |
||
121 | break |
||
122 | # find entries |
||
123 | state = None |
||
124 | for line in lines: |
||
125 | if state and not re.match(r'^\s\s\S', line): |
||
126 | yield (state, line.strip()) |
||
127 | state = None |
||
128 | elif re.match(r'^\s\s\S+$', line.rstrip()): |
||
129 | state = line.strip() |
||
130 | else: |
||
131 | pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)') |
||
132 | match = pattern.match(line.rstrip()) |
||
133 | if match: |
||
134 | current = match.groupdict() |
||
135 | yield (current['key'], current['value']) |
||
136 | |||
137 | |||
138 | def get_checkers(clang, plugins): |
||
139 | """ Get all the available checkers from default and from the plugins. |
||
140 | |||
141 | :param clang: the compiler we are using |
||
142 | :param plugins: list of plugins which was requested by the user |
||
143 | :return: a dictionary of all available checkers and its status |
||
144 | |||
145 | {<checker name>: (<checker description>, <is active by default>)} """ |
||
146 | |||
147 | load = [elem for plugin in plugins for elem in ['-load', plugin]] |
||
148 | cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help'] |
||
149 | |||
150 | lines = run_command(cmd) |
||
151 | |||
152 | is_active_checker = is_active(get_active_checkers(clang, plugins)) |
||
153 | |||
154 | checkers = { |
||
155 | name: (description, is_active_checker(name)) |
||
156 | for name, description in parse_checkers(lines) |
||
157 | } |
||
158 | if not checkers: |
||
159 | raise Exception('Could not query Clang for available checkers.') |
||
160 | |||
161 | return checkers |
||
162 | |||
163 | |||
164 | def is_ctu_capable(extdef_map_cmd): |
||
165 | """ Detects if the current (or given) clang and external definition mapping |
||
166 | executables are CTU compatible. """ |
||
167 | |||
168 | try: |
||
169 | run_command([extdef_map_cmd, '-version']) |
||
170 | except (OSError, subprocess.CalledProcessError): |
||
171 | return False |
||
172 | return True |
||
173 | |||
174 | |||
175 | def get_triple_arch(command, cwd): |
||
176 | """Returns the architecture part of the target triple for the given |
||
177 | compilation command. """ |
||
178 | |||
179 | cmd = get_arguments(command, cwd) |
||
180 | try: |
||
181 | separator = cmd.index("-triple") |
||
182 | return cmd[separator + 1] |
||
183 | except (IndexError, ValueError): |
||
184 | return "" |