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 | |
| 18 | |
| 19 | def get_version(cmd): |
| 20 | """ Returns the compiler version as string. """ |
| 21 | |
| 22 | lines = subprocess.check_output([cmd, '-v'], stderr=subprocess.STDOUT) |
| 23 | return lines.decode('ascii').splitlines()[0] |
| 24 | |
| 25 | |
| 26 | def get_arguments(command, cwd): |
| 27 | """ Capture Clang invocation. |
| 28 | |
| 29 | This method returns the front-end invocation that would be executed as |
| 30 | a result of the given driver invocation. """ |
| 31 | |
| 32 | def lastline(stream): |
| 33 | last = None |
| 34 | for line in stream: |
| 35 | last = line |
| 36 | if last is None: |
| 37 | raise Exception("output not found") |
| 38 | return last |
| 39 | |
| 40 | cmd = command[:] |
| 41 | cmd.insert(1, '-###') |
| 42 | logging.debug('exec command in %s: %s', cwd, ' '.join(cmd)) |
| 43 | child = subprocess.Popen(cmd, |
| 44 | cwd=cwd, |
| 45 | universal_newlines=True, |
| 46 | stdout=subprocess.PIPE, |
| 47 | stderr=subprocess.STDOUT) |
| 48 | line = lastline(child.stdout) |
| 49 | child.stdout.close() |
| 50 | child.wait() |
| 51 | if child.returncode == 0: |
| 52 | if re.search(r'clang(.*): error:', line): |
| 53 | raise Exception(line) |
| 54 | return decode(line) |
| 55 | else: |
| 56 | raise Exception(line) |
| 57 | |
| 58 | |
| 59 | def get_active_checkers(clang, plugins): |
| 60 | """ To get the default plugins we execute Clang to print how this |
| 61 | compilation would be called. |
| 62 | |
| 63 | For input file we specify stdin and pass only language information. """ |
| 64 | |
| 65 | def checkers(language): |
| 66 | """ Returns a list of active checkers for the given language. """ |
| 67 | |
| 68 | load = [elem |
| 69 | for plugin in plugins |
| 70 | for elem in ['-Xclang', '-load', '-Xclang', plugin]] |
| 71 | cmd = [clang, '--analyze'] + load + ['-x', language, '-'] |
| 72 | pattern = re.compile(r'^-analyzer-checker=(.*)$') |
| 73 | return [pattern.match(arg).group(1) |
| 74 | for arg in get_arguments(cmd, '.') if pattern.match(arg)] |
| 75 | |
| 76 | result = set() |
| 77 | for language in ['c', 'c++', 'objective-c', 'objective-c++']: |
| 78 | result.update(checkers(language)) |
| 79 | return result |
| 80 | |
| 81 | |
| 82 | def get_checkers(clang, plugins): |
| 83 | """ Get all the available checkers from default and from the plugins. |
| 84 | |
| 85 | clang -- the compiler we are using |
| 86 | plugins -- list of plugins which was requested by the user |
| 87 | |
| 88 | This method returns a dictionary of all available checkers and status. |
| 89 | |
| 90 | {<plugin name>: (<plugin description>, <is active by default>)} """ |
| 91 | |
| 92 | plugins = plugins if plugins else [] |
| 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 plugins has the |
| 99 | name and the description in separate lines. |
| 100 | |
| 101 | The plugin 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 | plugin. The description ends with a newline character. """ |
| 105 | |
| 106 | # find checkers header |
| 107 | for line in stream: |
| 108 | if re.match(r'^CHECKERS:', line): |
| 109 | break |
| 110 | # find entries |
| 111 | state = None |
| 112 | for line in stream: |
| 113 | if state and not re.match(r'^\s\s\S', line): |
| 114 | yield (state, line.strip()) |
| 115 | state = None |
| 116 | elif re.match(r'^\s\s\S+$', line.rstrip()): |
| 117 | state = line.strip() |
| 118 | else: |
| 119 | pattern = re.compile(r'^\s\s(?P<key>\S*)\s*(?P<value>.*)') |
| 120 | match = pattern.match(line.rstrip()) |
| 121 | if match: |
| 122 | current = match.groupdict() |
| 123 | yield (current['key'], current['value']) |
| 124 | |
| 125 | def is_active(actives, entry): |
| 126 | """ Returns true if plugin name is matching the active plugin names. |
| 127 | |
| 128 | actives -- set of active plugin names (or prefixes). |
| 129 | entry -- the current plugin name to judge. |
| 130 | |
| 131 | The active plugin names are specific plugin names or prefix of some |
| 132 | names. One example for prefix, when it say 'unix' and it shall match |
| 133 | on 'unix.API', 'unix.Malloc' and 'unix.MallocSizeof'. """ |
| 134 | |
| 135 | return any(re.match(r'^' + a + r'(\.|$)', entry) for a in actives) |
| 136 | |
| 137 | actives = get_active_checkers(clang, plugins) |
| 138 | |
| 139 | load = [elem for plugin in plugins for elem in ['-load', plugin]] |
| 140 | cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help'] |
| 141 | |
| 142 | logging.debug('exec command: %s', ' '.join(cmd)) |
| 143 | child = subprocess.Popen(cmd, |
| 144 | universal_newlines=True, |
| 145 | stdout=subprocess.PIPE, |
| 146 | stderr=subprocess.STDOUT) |
| 147 | checkers = { |
| 148 | k: (v, is_active(actives, k)) |
| 149 | for k, v in parse_checkers(child.stdout) |
| 150 | } |
| 151 | child.stdout.close() |
| 152 | child.wait() |
| 153 | if child.returncode == 0 and len(checkers): |
| 154 | return checkers |
| 155 | else: |
| 156 | raise Exception('Could not query Clang for available checkers.') |