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 8Since Clang command line interface is so rich, but this project is using only 9a subset of that, it makes sense to create a function specific wrapper. """ 10 11import re 12import subprocess 13import logging 14from libscanbuild.shell import decode 15 16__all__ = ['get_version', 'get_arguments', 'get_checkers'] 17 18 19def 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 26def 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 59def 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 82def 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.') 157