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