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