17330f729Sjoerg# -*- coding: utf-8 -*- 27330f729Sjoerg# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 37330f729Sjoerg# See https://llvm.org/LICENSE.txt for license information. 47330f729Sjoerg# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 57330f729Sjoerg""" This module is responsible for the Clang executable. 67330f729Sjoerg 77330f729SjoergSince Clang command line interface is so rich, but this project is using only 87330f729Sjoerga subset of that, it makes sense to create a function specific wrapper. """ 97330f729Sjoerg 107330f729Sjoergimport subprocess 117330f729Sjoergimport re 127330f729Sjoergfrom libscanbuild import run_command 137330f729Sjoergfrom libscanbuild.shell import decode 147330f729Sjoerg 157330f729Sjoerg__all__ = ['get_version', 'get_arguments', 'get_checkers', 'is_ctu_capable', 167330f729Sjoerg 'get_triple_arch'] 177330f729Sjoerg 187330f729Sjoerg# regex for activated checker 197330f729SjoergACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$') 207330f729Sjoerg 217330f729Sjoerg 22*e038c9c4Sjoergclass ClangErrorException(Exception): 23*e038c9c4Sjoerg def __init__(self, error): 24*e038c9c4Sjoerg self.error = error 25*e038c9c4Sjoerg 26*e038c9c4Sjoerg 277330f729Sjoergdef get_version(clang): 287330f729Sjoerg """ Returns the compiler version as string. 297330f729Sjoerg 307330f729Sjoerg :param clang: the compiler we are using 317330f729Sjoerg :return: the version string printed to stderr """ 327330f729Sjoerg 337330f729Sjoerg output = run_command([clang, '-v']) 347330f729Sjoerg # the relevant version info is in the first line 357330f729Sjoerg return output[0] 367330f729Sjoerg 377330f729Sjoerg 387330f729Sjoergdef get_arguments(command, cwd): 397330f729Sjoerg """ Capture Clang invocation. 407330f729Sjoerg 417330f729Sjoerg :param command: the compilation command 427330f729Sjoerg :param cwd: the current working directory 437330f729Sjoerg :return: the detailed front-end invocation command """ 447330f729Sjoerg 457330f729Sjoerg cmd = command[:] 467330f729Sjoerg cmd.insert(1, '-###') 47*e038c9c4Sjoerg cmd.append('-fno-color-diagnostics') 487330f729Sjoerg 497330f729Sjoerg output = run_command(cmd, cwd=cwd) 507330f729Sjoerg # The relevant information is in the last line of the output. 517330f729Sjoerg # Don't check if finding last line fails, would throw exception anyway. 527330f729Sjoerg last_line = output[-1] 537330f729Sjoerg if re.search(r'clang(.*): error:', last_line): 54*e038c9c4Sjoerg raise ClangErrorException(last_line) 557330f729Sjoerg return decode(last_line) 567330f729Sjoerg 577330f729Sjoerg 587330f729Sjoergdef get_active_checkers(clang, plugins): 597330f729Sjoerg """ Get the active checker list. 607330f729Sjoerg 617330f729Sjoerg :param clang: the compiler we are using 627330f729Sjoerg :param plugins: list of plugins which was requested by the user 637330f729Sjoerg :return: list of checker names which are active 647330f729Sjoerg 657330f729Sjoerg To get the default checkers we execute Clang to print how this 667330f729Sjoerg compilation would be called. And take out the enabled checker from the 677330f729Sjoerg arguments. For input file we specify stdin and pass only language 687330f729Sjoerg information. """ 697330f729Sjoerg 707330f729Sjoerg def get_active_checkers_for(language): 717330f729Sjoerg """ Returns a list of active checkers for the given language. """ 727330f729Sjoerg 737330f729Sjoerg load_args = [arg 747330f729Sjoerg for plugin in plugins 757330f729Sjoerg for arg in ['-Xclang', '-load', '-Xclang', plugin]] 767330f729Sjoerg cmd = [clang, '--analyze'] + load_args + ['-x', language, '-'] 777330f729Sjoerg return [ACTIVE_CHECKER_PATTERN.match(arg).group(1) 787330f729Sjoerg for arg in get_arguments(cmd, '.') 797330f729Sjoerg if ACTIVE_CHECKER_PATTERN.match(arg)] 807330f729Sjoerg 817330f729Sjoerg result = set() 827330f729Sjoerg for language in ['c', 'c++', 'objective-c', 'objective-c++']: 837330f729Sjoerg result.update(get_active_checkers_for(language)) 847330f729Sjoerg return frozenset(result) 857330f729Sjoerg 867330f729Sjoerg 877330f729Sjoergdef is_active(checkers): 887330f729Sjoerg """ Returns a method, which classifies the checker active or not, 897330f729Sjoerg based on the received checker name list. """ 907330f729Sjoerg 917330f729Sjoerg def predicate(checker): 927330f729Sjoerg """ Returns True if the given checker is active. """ 937330f729Sjoerg 947330f729Sjoerg return any(pattern.match(checker) for pattern in predicate.patterns) 957330f729Sjoerg 967330f729Sjoerg predicate.patterns = [re.compile(r'^' + a + r'(\.|$)') for a in checkers] 977330f729Sjoerg return predicate 987330f729Sjoerg 997330f729Sjoerg 1007330f729Sjoergdef parse_checkers(stream): 1017330f729Sjoerg """ Parse clang -analyzer-checker-help output. 1027330f729Sjoerg 1037330f729Sjoerg Below the line 'CHECKERS:' are there the name description pairs. 1047330f729Sjoerg Many of them are in one line, but some long named checker has the 1057330f729Sjoerg name and the description in separate lines. 1067330f729Sjoerg 1077330f729Sjoerg The checker name is always prefixed with two space character. The 1087330f729Sjoerg name contains no whitespaces. Then followed by newline (if it's 1097330f729Sjoerg too long) or other space characters comes the description of the 1107330f729Sjoerg checker. The description ends with a newline character. 1117330f729Sjoerg 1127330f729Sjoerg :param stream: list of lines to parse 1137330f729Sjoerg :return: generator of tuples 1147330f729Sjoerg 1157330f729Sjoerg (<checker name>, <checker description>) """ 1167330f729Sjoerg 1177330f729Sjoerg lines = iter(stream) 1187330f729Sjoerg # find checkers header 1197330f729Sjoerg for line in lines: 1207330f729Sjoerg if re.match(r'^CHECKERS:', line): 1217330f729Sjoerg break 1227330f729Sjoerg # find entries 1237330f729Sjoerg state = None 1247330f729Sjoerg for line in lines: 1257330f729Sjoerg if state and not re.match(r'^\s\s\S', line): 1267330f729Sjoerg yield (state, line.strip()) 1277330f729Sjoerg state = None 1287330f729Sjoerg elif re.match(r'^\s\s\S+$', line.rstrip()): 1297330f729Sjoerg state = line.strip() 1307330f729Sjoerg else: 1317330f729Sjoerg pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)') 1327330f729Sjoerg match = pattern.match(line.rstrip()) 1337330f729Sjoerg if match: 1347330f729Sjoerg current = match.groupdict() 1357330f729Sjoerg yield (current['key'], current['value']) 1367330f729Sjoerg 1377330f729Sjoerg 1387330f729Sjoergdef get_checkers(clang, plugins): 1397330f729Sjoerg """ Get all the available checkers from default and from the plugins. 1407330f729Sjoerg 1417330f729Sjoerg :param clang: the compiler we are using 1427330f729Sjoerg :param plugins: list of plugins which was requested by the user 1437330f729Sjoerg :return: a dictionary of all available checkers and its status 1447330f729Sjoerg 1457330f729Sjoerg {<checker name>: (<checker description>, <is active by default>)} """ 1467330f729Sjoerg 1477330f729Sjoerg load = [elem for plugin in plugins for elem in ['-load', plugin]] 1487330f729Sjoerg cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help'] 1497330f729Sjoerg 1507330f729Sjoerg lines = run_command(cmd) 1517330f729Sjoerg 1527330f729Sjoerg is_active_checker = is_active(get_active_checkers(clang, plugins)) 1537330f729Sjoerg 1547330f729Sjoerg checkers = { 1557330f729Sjoerg name: (description, is_active_checker(name)) 1567330f729Sjoerg for name, description in parse_checkers(lines) 1577330f729Sjoerg } 1587330f729Sjoerg if not checkers: 1597330f729Sjoerg raise Exception('Could not query Clang for available checkers.') 1607330f729Sjoerg 1617330f729Sjoerg return checkers 1627330f729Sjoerg 1637330f729Sjoerg 1647330f729Sjoergdef is_ctu_capable(extdef_map_cmd): 1657330f729Sjoerg """ Detects if the current (or given) clang and external definition mapping 1667330f729Sjoerg executables are CTU compatible. """ 1677330f729Sjoerg 1687330f729Sjoerg try: 1697330f729Sjoerg run_command([extdef_map_cmd, '-version']) 1707330f729Sjoerg except (OSError, subprocess.CalledProcessError): 1717330f729Sjoerg return False 1727330f729Sjoerg return True 1737330f729Sjoerg 1747330f729Sjoerg 1757330f729Sjoergdef get_triple_arch(command, cwd): 1767330f729Sjoerg """Returns the architecture part of the target triple for the given 1777330f729Sjoerg compilation command. """ 1787330f729Sjoerg 1797330f729Sjoerg cmd = get_arguments(command, cwd) 1807330f729Sjoerg try: 1817330f729Sjoerg separator = cmd.index("-triple") 1827330f729Sjoerg return cmd[separator + 1] 1837330f729Sjoerg except (IndexError, ValueError): 1847330f729Sjoerg return "" 185