Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | # The LLVM Compiler Infrastructure |
| 3 | # |
| 4 | # This file is distributed under the University of Illinois Open Source |
| 5 | # License. See LICENSE.TXT for details. |
| 6 | """ This module is responsible for the Clang executable. |
| 7 | |
| 8 | Since Clang command line interface is so rich, but this project is using only |
| 9 | a subset of that, it makes sense to create a function specific wrapper. """ |
| 10 | |
| 11 | import re |
| 12 | import subprocess |
| 13 | import logging |
| 14 | from libscanbuild.shell import decode |
| 15 | |
| 16 | __all__ = ['get_version', 'get_arguments', 'get_checkers'] |
| 17 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 18 | # regex for activated checker |
| 19 | ACTIVE_CHECKER_PATTERN = re.compile(r'^-analyzer-checker=(.*)$') |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 20 | |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 21 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 22 | def get_version(clang): |
| 23 | """ Returns the compiler version as string. |
| 24 | |
| 25 | :param clang: the compiler we are using |
| 26 | :return: the version string printed to stderr """ |
| 27 | |
| 28 | output = subprocess.check_output([clang, '-v'], stderr=subprocess.STDOUT) |
| 29 | return output.decode('utf-8').splitlines()[0] |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 30 | |
| 31 | |
| 32 | def get_arguments(command, cwd): |
| 33 | """ Capture Clang invocation. |
| 34 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 35 | :param command: the compilation command |
| 36 | :param cwd: the current working directory |
| 37 | :return: the detailed front-end invocation command """ |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 38 | |
| 39 | cmd = command[:] |
| 40 | cmd.insert(1, '-###') |
| 41 | logging.debug('exec command in %s: %s', cwd, ' '.join(cmd)) |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 42 | |
| 43 | output = subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT) |
| 44 | # The relevant information is in the last line of the output. |
| 45 | # Don't check if finding last line fails, would throw exception anyway. |
| 46 | last_line = output.decode('utf-8').splitlines()[-1] |
| 47 | if re.search(r'clang(.*): error:', last_line): |
| 48 | raise Exception(last_line) |
| 49 | return decode(last_line) |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 50 | |
| 51 | |
| 52 | def get_active_checkers(clang, plugins): |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 53 | """ Get the active checker list. |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 54 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 55 | :param clang: the compiler we are using |
| 56 | :param plugins: list of plugins which was requested by the user |
| 57 | :return: list of checker names which are active |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 58 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 59 | To get the default checkers we execute Clang to print how this |
| 60 | compilation would be called. And take out the enabled checker from the |
| 61 | arguments. For input file we specify stdin and pass only language |
| 62 | information. """ |
| 63 | |
| 64 | def get_active_checkers_for(language): |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 65 | """ Returns a list of active checkers for the given language. """ |
| 66 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 67 | load_args = [arg |
| 68 | for plugin in plugins |
| 69 | for arg in ['-Xclang', '-load', '-Xclang', plugin]] |
| 70 | cmd = [clang, '--analyze'] + load_args + ['-x', language, '-'] |
| 71 | return [ACTIVE_CHECKER_PATTERN.match(arg).group(1) |
| 72 | for arg in get_arguments(cmd, '.') |
| 73 | if ACTIVE_CHECKER_PATTERN.match(arg)] |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 74 | |
| 75 | result = set() |
| 76 | for language in ['c', 'c++', 'objective-c', 'objective-c++']: |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 77 | result.update(get_active_checkers_for(language)) |
| 78 | return frozenset(result) |
| 79 | |
| 80 | |
| 81 | def is_active(checkers): |
| 82 | """ Returns a method, which classifies the checker active or not, |
| 83 | based on the received checker name list. """ |
| 84 | |
| 85 | def predicate(checker): |
| 86 | """ Returns True if the given checker is active. """ |
| 87 | |
| 88 | return any(pattern.match(checker) for pattern in predicate.patterns) |
| 89 | |
| 90 | predicate.patterns = [re.compile(r'^' + a + r'(\.|$)') for a in checkers] |
| 91 | return predicate |
| 92 | |
| 93 | |
| 94 | def parse_checkers(stream): |
| 95 | """ Parse clang -analyzer-checker-help output. |
| 96 | |
| 97 | Below the line 'CHECKERS:' are there the name description pairs. |
| 98 | Many of them are in one line, but some long named checker has the |
| 99 | name and the description in separate lines. |
| 100 | |
| 101 | The checker name is always prefixed with two space character. The |
| 102 | name contains no whitespaces. Then followed by newline (if it's |
| 103 | too long) or other space characters comes the description of the |
| 104 | checker. The description ends with a newline character. |
| 105 | |
| 106 | :param stream: list of lines to parse |
| 107 | :return: generator of tuples |
| 108 | |
| 109 | (<checker name>, <checker description>) """ |
| 110 | |
| 111 | lines = iter(stream) |
| 112 | # find checkers header |
| 113 | for line in lines: |
| 114 | if re.match(r'^CHECKERS:', line): |
| 115 | break |
| 116 | # find entries |
| 117 | state = None |
| 118 | for line in lines: |
| 119 | if state and not re.match(r'^\s\s\S', line): |
| 120 | yield (state, line.strip()) |
| 121 | state = None |
| 122 | elif re.match(r'^\s\s\S+$', line.rstrip()): |
| 123 | state = line.strip() |
| 124 | else: |
| 125 | pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)') |
| 126 | match = pattern.match(line.rstrip()) |
| 127 | if match: |
| 128 | current = match.groupdict() |
| 129 | yield (current['key'], current['value']) |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 130 | |
| 131 | |
| 132 | def get_checkers(clang, plugins): |
| 133 | """ Get all the available checkers from default and from the plugins. |
| 134 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 135 | :param clang: the compiler we are using |
| 136 | :param plugins: list of plugins which was requested by the user |
| 137 | :return: a dictionary of all available checkers and its status |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 138 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 139 | {<checker name>: (<checker description>, <is active by default>)} """ |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 140 | |
| 141 | load = [elem for plugin in plugins for elem in ['-load', plugin]] |
| 142 | cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help'] |
| 143 | |
| 144 | logging.debug('exec command: %s', ' '.join(cmd)) |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 145 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
| 146 | lines = output.decode('utf-8').splitlines() |
| 147 | |
| 148 | is_active_checker = is_active(get_active_checkers(clang, plugins)) |
| 149 | |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 150 | checkers = { |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 151 | name: (description, is_active_checker(name)) |
| 152 | for name, description in parse_checkers(lines) |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 153 | } |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 154 | if not checkers: |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 155 | raise Exception('Could not query Clang for available checkers.') |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 156 | |
| 157 | return checkers |