diff options
Diffstat (limited to 'gnu/llvm/clang/tools/scan-build-py/libscanbuild/clang.py')
-rw-r--r-- | gnu/llvm/clang/tools/scan-build-py/libscanbuild/clang.py | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/gnu/llvm/clang/tools/scan-build-py/libscanbuild/clang.py b/gnu/llvm/clang/tools/scan-build-py/libscanbuild/clang.py new file mode 100644 index 00000000000..4f02cb20d3f --- /dev/null +++ b/gnu/llvm/clang/tools/scan-build-py/libscanbuild/clang.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +""" This module is responsible for the Clang executable. + +Since Clang command line interface is so rich, but this project is using only +a subset of that, it makes sense to create a function specific wrapper. """ + +import subprocess +import re +from libscanbuild import run_command +from libscanbuild.shell import decode + +__all__ = ['get_version', 'get_arguments', 'get_checkers', 'is_ctu_capable', + 'get_triple_arch'] + +# regex for activated checker +ACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$') + + +class ClangErrorException(Exception): + def __init__(self, error): + self.error = error + + +def get_version(clang): + """ Returns the compiler version as string. + + :param clang: the compiler we are using + :return: the version string printed to stderr """ + + output = run_command([clang, '-v']) + # the relevant version info is in the first line + return output[0] + + +def get_arguments(command, cwd): + """ Capture Clang invocation. + + :param command: the compilation command + :param cwd: the current working directory + :return: the detailed front-end invocation command """ + + cmd = command[:] + cmd.insert(1, '-###') + cmd.append('-fno-color-diagnostics') + + output = run_command(cmd, cwd=cwd) + # The relevant information is in the last line of the output. + # Don't check if finding last line fails, would throw exception anyway. + last_line = output[-1] + if re.search(r'clang(.*): error:', last_line): + raise ClangErrorException(last_line) + return decode(last_line) + + +def get_active_checkers(clang, plugins): + """ Get the active checker list. + + :param clang: the compiler we are using + :param plugins: list of plugins which was requested by the user + :return: list of checker names which are active + + To get the default checkers we execute Clang to print how this + compilation would be called. And take out the enabled checker from the + arguments. For input file we specify stdin and pass only language + information. """ + + def get_active_checkers_for(language): + """ Returns a list of active checkers for the given language. """ + + load_args = [arg + for plugin in plugins + for arg in ['-Xclang', '-load', '-Xclang', plugin]] + cmd = [clang, '--analyze'] + load_args + ['-x', language, '-'] + return [ACTIVE_CHECKER_PATTERN.match(arg).group(1) + for arg in get_arguments(cmd, '.') + if ACTIVE_CHECKER_PATTERN.match(arg)] + + result = set() + for language in ['c', 'c++', 'objective-c', 'objective-c++']: + result.update(get_active_checkers_for(language)) + return frozenset(result) + + +def is_active(checkers): + """ Returns a method, which classifies the checker active or not, + based on the received checker name list. """ + + def predicate(checker): + """ Returns True if the given checker is active. """ + + return any(pattern.match(checker) for pattern in predicate.patterns) + + predicate.patterns = [re.compile(r'^' + a + r'(\.|$)') for a in checkers] + return predicate + + +def parse_checkers(stream): + """ Parse clang -analyzer-checker-help output. + + Below the line 'CHECKERS:' are there the name description pairs. + Many of them are in one line, but some long named checker has the + name and the description in separate lines. + + The checker name is always prefixed with two space character. The + name contains no whitespaces. Then followed by newline (if it's + too long) or other space characters comes the description of the + checker. The description ends with a newline character. + + :param stream: list of lines to parse + :return: generator of tuples + + (<checker name>, <checker description>) """ + + lines = iter(stream) + # find checkers header + for line in lines: + if re.match(r'^CHECKERS:', line): + break + # find entries + state = None + for line in lines: + if state and not re.match(r'^\s\s\S', line): + yield (state, line.strip()) + state = None + elif re.match(r'^\s\s\S+$', line.rstrip()): + state = line.strip() + else: + pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)') + match = pattern.match(line.rstrip()) + if match: + current = match.groupdict() + yield (current['key'], current['value']) + + +def get_checkers(clang, plugins): + """ Get all the available checkers from default and from the plugins. + + :param clang: the compiler we are using + :param plugins: list of plugins which was requested by the user + :return: a dictionary of all available checkers and its status + + {<checker name>: (<checker description>, <is active by default>)} """ + + load = [elem for plugin in plugins for elem in ['-load', plugin]] + cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help'] + + lines = run_command(cmd) + + is_active_checker = is_active(get_active_checkers(clang, plugins)) + + checkers = { + name: (description, is_active_checker(name)) + for name, description in parse_checkers(lines) + } + if not checkers: + raise Exception('Could not query Clang for available checkers.') + + return checkers + + +def is_ctu_capable(extdef_map_cmd): + """ Detects if the current (or given) clang and external definition mapping + executables are CTU compatible. """ + + try: + run_command([extdef_map_cmd, '-version']) + except (OSError, subprocess.CalledProcessError): + return False + return True + + +def get_triple_arch(command, cwd): + """Returns the architecture part of the target triple for the given + compilation command. """ + + cmd = get_arguments(command, cwd) + try: + separator = cmd.index("-triple") + return cmd[separator + 1] + except (IndexError, ValueError): + return "" |