xref: /netbsd-src/external/apache2/llvm/dist/clang/tools/scan-build-py/libscanbuild/clang.py (revision e038c9c4676b0f19b1b7dd08a940c6ed64a6d5ae)
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