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