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__ = ['get_version', 'get_arguments', 'get_checkers', 'is_ctu_capable', 16 'get_triple_arch'] 17 18# regex for activated checker 19ACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$') 20 21 22class ClangErrorException(Exception): 23 def __init__(self, error): 24 self.error = error 25 26 27def 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 38def 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 58def 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 87def 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 100def 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 138def 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 164def 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 175def 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 "" 185