| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,260 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
| """ This module compiles the intercept library. """ | ||
|
|
||
| import sys | ||
| import os | ||
| import os.path | ||
| import re | ||
| import tempfile | ||
| import shutil | ||
| import contextlib | ||
| import logging | ||
|
|
||
| __all__ = ['build_libear'] | ||
|
|
||
|
|
||
| def build_libear(compiler, dst_dir): | ||
| """ Returns the full path to the 'libear' library. """ | ||
|
|
||
| try: | ||
| src_dir = os.path.dirname(os.path.realpath(__file__)) | ||
| toolset = make_toolset(src_dir) | ||
| toolset.set_compiler(compiler) | ||
| toolset.set_language_standard('c99') | ||
| toolset.add_definitions(['-D_GNU_SOURCE']) | ||
|
|
||
| configure = do_configure(toolset) | ||
| configure.check_function_exists('execve', 'HAVE_EXECVE') | ||
| configure.check_function_exists('execv', 'HAVE_EXECV') | ||
| configure.check_function_exists('execvpe', 'HAVE_EXECVPE') | ||
| configure.check_function_exists('execvp', 'HAVE_EXECVP') | ||
| configure.check_function_exists('execvP', 'HAVE_EXECVP2') | ||
| configure.check_function_exists('exect', 'HAVE_EXECT') | ||
| configure.check_function_exists('execl', 'HAVE_EXECL') | ||
| configure.check_function_exists('execlp', 'HAVE_EXECLP') | ||
| configure.check_function_exists('execle', 'HAVE_EXECLE') | ||
| configure.check_function_exists('posix_spawn', 'HAVE_POSIX_SPAWN') | ||
| configure.check_function_exists('posix_spawnp', 'HAVE_POSIX_SPAWNP') | ||
| configure.check_symbol_exists('_NSGetEnviron', 'crt_externs.h', | ||
| 'HAVE_NSGETENVIRON') | ||
| configure.write_by_template( | ||
| os.path.join(src_dir, 'config.h.in'), | ||
| os.path.join(dst_dir, 'config.h')) | ||
|
|
||
| target = create_shared_library('ear', toolset) | ||
| target.add_include(dst_dir) | ||
| target.add_sources('ear.c') | ||
| target.link_against(toolset.dl_libraries()) | ||
| target.link_against(['pthread']) | ||
| target.build_release(dst_dir) | ||
|
|
||
| return os.path.join(dst_dir, target.name) | ||
|
|
||
| except Exception: | ||
| logging.info("Could not build interception library.", exc_info=True) | ||
| return None | ||
|
|
||
|
|
||
| def execute(cmd, *args, **kwargs): | ||
| """ Make subprocess execution silent. """ | ||
|
|
||
| import subprocess | ||
| kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}) | ||
| return subprocess.check_call(cmd, *args, **kwargs) | ||
|
|
||
|
|
||
| @contextlib.contextmanager | ||
| def TemporaryDirectory(**kwargs): | ||
| name = tempfile.mkdtemp(**kwargs) | ||
| try: | ||
| yield name | ||
| finally: | ||
| shutil.rmtree(name) | ||
|
|
||
|
|
||
| class Toolset(object): | ||
| """ Abstract class to represent different toolset. """ | ||
|
|
||
| def __init__(self, src_dir): | ||
| self.src_dir = src_dir | ||
| self.compiler = None | ||
| self.c_flags = [] | ||
|
|
||
| def set_compiler(self, compiler): | ||
| """ part of public interface """ | ||
| self.compiler = compiler | ||
|
|
||
| def set_language_standard(self, standard): | ||
| """ part of public interface """ | ||
| self.c_flags.append('-std=' + standard) | ||
|
|
||
| def add_definitions(self, defines): | ||
| """ part of public interface """ | ||
| self.c_flags.extend(defines) | ||
|
|
||
| def dl_libraries(self): | ||
| raise NotImplementedError() | ||
|
|
||
| def shared_library_name(self, name): | ||
| raise NotImplementedError() | ||
|
|
||
| def shared_library_c_flags(self, release): | ||
| extra = ['-DNDEBUG', '-O3'] if release else [] | ||
| return extra + ['-fPIC'] + self.c_flags | ||
|
|
||
| def shared_library_ld_flags(self, release, name): | ||
| raise NotImplementedError() | ||
|
|
||
|
|
||
| class DarwinToolset(Toolset): | ||
| def __init__(self, src_dir): | ||
| Toolset.__init__(self, src_dir) | ||
|
|
||
| def dl_libraries(self): | ||
| return [] | ||
|
|
||
| def shared_library_name(self, name): | ||
| return 'lib' + name + '.dylib' | ||
|
|
||
| def shared_library_ld_flags(self, release, name): | ||
| extra = ['-dead_strip'] if release else [] | ||
| return extra + ['-dynamiclib', '-install_name', '@rpath/' + name] | ||
|
|
||
|
|
||
| class UnixToolset(Toolset): | ||
| def __init__(self, src_dir): | ||
| Toolset.__init__(self, src_dir) | ||
|
|
||
| def dl_libraries(self): | ||
| return [] | ||
|
|
||
| def shared_library_name(self, name): | ||
| return 'lib' + name + '.so' | ||
|
|
||
| def shared_library_ld_flags(self, release, name): | ||
| extra = [] if release else [] | ||
| return extra + ['-shared', '-Wl,-soname,' + name] | ||
|
|
||
|
|
||
| class LinuxToolset(UnixToolset): | ||
| def __init__(self, src_dir): | ||
| UnixToolset.__init__(self, src_dir) | ||
|
|
||
| def dl_libraries(self): | ||
| return ['dl'] | ||
|
|
||
|
|
||
| def make_toolset(src_dir): | ||
| platform = sys.platform | ||
| if platform in {'win32', 'cygwin'}: | ||
| raise RuntimeError('not implemented on this platform') | ||
| elif platform == 'darwin': | ||
| return DarwinToolset(src_dir) | ||
| elif platform in {'linux', 'linux2'}: | ||
| return LinuxToolset(src_dir) | ||
| else: | ||
| return UnixToolset(src_dir) | ||
|
|
||
|
|
||
| class Configure(object): | ||
| def __init__(self, toolset): | ||
| self.ctx = toolset | ||
| self.results = {'APPLE': sys.platform == 'darwin'} | ||
|
|
||
| def _try_to_compile_and_link(self, source): | ||
| try: | ||
| with TemporaryDirectory() as work_dir: | ||
| src_file = 'check.c' | ||
| with open(os.path.join(work_dir, src_file), 'w') as handle: | ||
| handle.write(source) | ||
|
|
||
| execute([self.ctx.compiler, src_file] + self.ctx.c_flags, | ||
| cwd=work_dir) | ||
| return True | ||
| except Exception: | ||
| return False | ||
|
|
||
| def check_function_exists(self, function, name): | ||
| template = "int FUNCTION(); int main() { return FUNCTION(); }" | ||
| source = template.replace("FUNCTION", function) | ||
|
|
||
| logging.debug('Checking function %s', function) | ||
| found = self._try_to_compile_and_link(source) | ||
| logging.debug('Checking function %s -- %s', function, | ||
| 'found' if found else 'not found') | ||
| self.results.update({name: found}) | ||
|
|
||
| def check_symbol_exists(self, symbol, include, name): | ||
| template = """#include <INCLUDE> | ||
| int main() { return ((int*)(&SYMBOL))[0]; }""" | ||
| source = template.replace('INCLUDE', include).replace("SYMBOL", symbol) | ||
|
|
||
| logging.debug('Checking symbol %s', symbol) | ||
| found = self._try_to_compile_and_link(source) | ||
| logging.debug('Checking symbol %s -- %s', symbol, | ||
| 'found' if found else 'not found') | ||
| self.results.update({name: found}) | ||
|
|
||
| def write_by_template(self, template, output): | ||
| def transform(line, definitions): | ||
|
|
||
| pattern = re.compile(r'^#cmakedefine\s+(\S+)') | ||
| m = pattern.match(line) | ||
| if m: | ||
| key = m.group(1) | ||
| if key not in definitions or not definitions[key]: | ||
| return '/* #undef {} */\n'.format(key) | ||
| else: | ||
| return '#define {}\n'.format(key) | ||
| return line | ||
|
|
||
| with open(template, 'r') as src_handle: | ||
| logging.debug('Writing config to %s', output) | ||
| with open(output, 'w') as dst_handle: | ||
| for line in src_handle: | ||
| dst_handle.write(transform(line, self.results)) | ||
|
|
||
|
|
||
| def do_configure(toolset): | ||
| return Configure(toolset) | ||
|
|
||
|
|
||
| class SharedLibrary(object): | ||
| def __init__(self, name, toolset): | ||
| self.name = toolset.shared_library_name(name) | ||
| self.ctx = toolset | ||
| self.inc = [] | ||
| self.src = [] | ||
| self.lib = [] | ||
|
|
||
| def add_include(self, directory): | ||
| self.inc.extend(['-I', directory]) | ||
|
|
||
| def add_sources(self, source): | ||
| self.src.append(source) | ||
|
|
||
| def link_against(self, libraries): | ||
| self.lib.extend(['-l' + lib for lib in libraries]) | ||
|
|
||
| def build_release(self, directory): | ||
| for src in self.src: | ||
| logging.debug('Compiling %s', src) | ||
| execute( | ||
| [self.ctx.compiler, '-c', os.path.join(self.ctx.src_dir, src), | ||
| '-o', src + '.o'] + self.inc + | ||
| self.ctx.shared_library_c_flags(True), | ||
| cwd=directory) | ||
| logging.debug('Linking %s', self.name) | ||
| execute( | ||
| [self.ctx.compiler] + [src + '.o' for src in self.src] + | ||
| ['-o', self.name] + self.lib + | ||
| self.ctx.shared_library_ld_flags(True, self.name), | ||
| cwd=directory) | ||
|
|
||
|
|
||
| def create_shared_library(name, toolset): | ||
| return SharedLibrary(name, toolset) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /* -*- coding: utf-8 -*- | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #cmakedefine HAVE_EXECVE | ||
| #cmakedefine HAVE_EXECV | ||
| #cmakedefine HAVE_EXECVPE | ||
| #cmakedefine HAVE_EXECVP | ||
| #cmakedefine HAVE_EXECVP2 | ||
| #cmakedefine HAVE_EXECT | ||
| #cmakedefine HAVE_EXECL | ||
| #cmakedefine HAVE_EXECLP | ||
| #cmakedefine HAVE_EXECLE | ||
| #cmakedefine HAVE_POSIX_SPAWN | ||
| #cmakedefine HAVE_POSIX_SPAWNP | ||
| #cmakedefine HAVE_NSGETENVIRON | ||
|
|
||
| #cmakedefine APPLE |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
| """ | ||
| This module responsible to run the Clang static analyzer against any build | ||
| and generate reports. | ||
| """ | ||
|
|
||
|
|
||
| def duplicate_check(method): | ||
| """ Predicate to detect duplicated entries. | ||
| Unique hash method can be use to detect duplicates. Entries are | ||
| represented as dictionaries, which has no default hash method. | ||
| This implementation uses a set datatype to store the unique hash values. | ||
| This method returns a method which can detect the duplicate values. """ | ||
|
|
||
| def predicate(entry): | ||
| entry_hash = predicate.unique(entry) | ||
| if entry_hash not in predicate.state: | ||
| predicate.state.add(entry_hash) | ||
| return False | ||
| return True | ||
|
|
||
| predicate.unique = method | ||
| predicate.state = set() | ||
| return predicate | ||
|
|
||
|
|
||
| def tempdir(): | ||
| """ Return the default temorary directory. """ | ||
|
|
||
| from os import getenv | ||
| return getenv('TMPDIR', getenv('TEMP', getenv('TMP', '/tmp'))) | ||
|
|
||
|
|
||
| def initialize_logging(verbose_level): | ||
| """ Output content controlled by the verbosity level. """ | ||
|
|
||
| import sys | ||
| import os.path | ||
| import logging | ||
| level = logging.WARNING - min(logging.WARNING, (10 * verbose_level)) | ||
|
|
||
| if verbose_level <= 3: | ||
| fmt_string = '{0}: %(levelname)s: %(message)s' | ||
| else: | ||
| fmt_string = '{0}: %(levelname)s: %(funcName)s: %(message)s' | ||
|
|
||
| program = os.path.basename(sys.argv[0]) | ||
| logging.basicConfig(format=fmt_string.format(program), level=level) | ||
|
|
||
|
|
||
| def command_entry_point(function): | ||
| """ Decorator for command entry points. """ | ||
|
|
||
| import functools | ||
| import logging | ||
|
|
||
| @functools.wraps(function) | ||
| def wrapper(*args, **kwargs): | ||
|
|
||
| exit_code = 127 | ||
| try: | ||
| exit_code = function(*args, **kwargs) | ||
| except KeyboardInterrupt: | ||
| logging.warning('Keyboard interupt') | ||
| except Exception: | ||
| logging.exception('Internal error.') | ||
| if logging.getLogger().isEnabledFor(logging.DEBUG): | ||
| logging.error("Please report this bug and attach the output " | ||
| "to the bug report") | ||
| else: | ||
| logging.error("Please run this command again and turn on " | ||
| "verbose mode (add '-vvv' as argument).") | ||
| finally: | ||
| return exit_code | ||
|
|
||
| return wrapper |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
| """ 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 re | ||
| import subprocess | ||
| import logging | ||
| from libscanbuild.shell import decode | ||
|
|
||
| __all__ = ['get_version', 'get_arguments', 'get_checkers'] | ||
|
|
||
|
|
||
| def get_version(cmd): | ||
| """ Returns the compiler version as string. """ | ||
|
|
||
| lines = subprocess.check_output([cmd, '-v'], stderr=subprocess.STDOUT) | ||
| return lines.decode('ascii').splitlines()[0] | ||
|
|
||
|
|
||
| def get_arguments(command, cwd): | ||
| """ Capture Clang invocation. | ||
| This method returns the front-end invocation that would be executed as | ||
| a result of the given driver invocation. """ | ||
|
|
||
| def lastline(stream): | ||
| last = None | ||
| for line in stream: | ||
| last = line | ||
| if last is None: | ||
| raise Exception("output not found") | ||
| return last | ||
|
|
||
| cmd = command[:] | ||
| cmd.insert(1, '-###') | ||
| logging.debug('exec command in %s: %s', cwd, ' '.join(cmd)) | ||
| child = subprocess.Popen(cmd, | ||
| cwd=cwd, | ||
| universal_newlines=True, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT) | ||
| line = lastline(child.stdout) | ||
| child.stdout.close() | ||
| child.wait() | ||
| if child.returncode == 0: | ||
| if re.search(r'clang(.*): error:', line): | ||
| raise Exception(line) | ||
| return decode(line) | ||
| else: | ||
| raise Exception(line) | ||
|
|
||
|
|
||
| def get_active_checkers(clang, plugins): | ||
| """ To get the default plugins we execute Clang to print how this | ||
| compilation would be called. | ||
| For input file we specify stdin and pass only language information. """ | ||
|
|
||
| def checkers(language): | ||
| """ Returns a list of active checkers for the given language. """ | ||
|
|
||
| load = [elem | ||
| for plugin in plugins | ||
| for elem in ['-Xclang', '-load', '-Xclang', plugin]] | ||
| cmd = [clang, '--analyze'] + load + ['-x', language, '-'] | ||
| pattern = re.compile(r'^-analyzer-checker=(.*)$') | ||
| return [pattern.match(arg).group(1) | ||
| for arg in get_arguments(cmd, '.') if pattern.match(arg)] | ||
|
|
||
| result = set() | ||
| for language in ['c', 'c++', 'objective-c', 'objective-c++']: | ||
| result.update(checkers(language)) | ||
| return result | ||
|
|
||
|
|
||
| def get_checkers(clang, plugins): | ||
| """ Get all the available checkers from default and from the plugins. | ||
| clang -- the compiler we are using | ||
| plugins -- list of plugins which was requested by the user | ||
| This method returns a dictionary of all available checkers and status. | ||
| {<plugin name>: (<plugin description>, <is active by default>)} """ | ||
|
|
||
| plugins = plugins if plugins else [] | ||
|
|
||
| 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 plugins has the | ||
| name and the description in separate lines. | ||
| The plugin 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 | ||
| plugin. The description ends with a newline character. """ | ||
|
|
||
| # find checkers header | ||
| for line in stream: | ||
| if re.match(r'^CHECKERS:', line): | ||
| break | ||
| # find entries | ||
| state = None | ||
| for line in stream: | ||
| 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 is_active(actives, entry): | ||
| """ Returns true if plugin name is matching the active plugin names. | ||
| actives -- set of active plugin names (or prefixes). | ||
| entry -- the current plugin name to judge. | ||
| The active plugin names are specific plugin names or prefix of some | ||
| names. One example for prefix, when it say 'unix' and it shall match | ||
| on 'unix.API', 'unix.Malloc' and 'unix.MallocSizeof'. """ | ||
|
|
||
| return any(re.match(r'^' + a + r'(\.|$)', entry) for a in actives) | ||
|
|
||
| actives = get_active_checkers(clang, plugins) | ||
|
|
||
| load = [elem for plugin in plugins for elem in ['-load', plugin]] | ||
| cmd = [clang, '-cc1'] + load + ['-analyzer-checker-help'] | ||
|
|
||
| logging.debug('exec command: %s', ' '.join(cmd)) | ||
| child = subprocess.Popen(cmd, | ||
| universal_newlines=True, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT) | ||
| checkers = { | ||
| k: (v, is_active(actives, k)) | ||
| for k, v in parse_checkers(child.stdout) | ||
| } | ||
| child.stdout.close() | ||
| child.wait() | ||
| if child.returncode == 0 and len(checkers): | ||
| return checkers | ||
| else: | ||
| raise Exception('Could not query Clang for available checkers.') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
| """ This module is responsible for to parse a compiler invocation. """ | ||
|
|
||
| import re | ||
| import os | ||
|
|
||
| __all__ = ['Action', 'classify_parameters', 'classify_source'] | ||
|
|
||
|
|
||
| class Action(object): | ||
| """ Enumeration class for compiler action. """ | ||
|
|
||
| Link, Compile, Ignored = range(3) | ||
|
|
||
|
|
||
| def classify_parameters(command): | ||
| """ Parses the command line arguments of the given invocation. """ | ||
|
|
||
| # result value of this method. | ||
| # some value are preset, some will be set only when found. | ||
| result = { | ||
| 'action': Action.Link, | ||
| 'files': [], | ||
| 'output': None, | ||
| 'compile_options': [], | ||
| 'c++': is_cplusplus_compiler(command[0]) | ||
| # archs_seen | ||
| # language | ||
| } | ||
|
|
||
| # data structure to ignore compiler parameters. | ||
| # key: parameter name, value: number of parameters to ignore afterwards. | ||
| ignored = { | ||
| '-g': 0, | ||
| '-fsyntax-only': 0, | ||
| '-save-temps': 0, | ||
| '-install_name': 1, | ||
| '-exported_symbols_list': 1, | ||
| '-current_version': 1, | ||
| '-compatibility_version': 1, | ||
| '-init': 1, | ||
| '-e': 1, | ||
| '-seg1addr': 1, | ||
| '-bundle_loader': 1, | ||
| '-multiply_defined': 1, | ||
| '-sectorder': 3, | ||
| '--param': 1, | ||
| '--serialize-diagnostics': 1 | ||
| } | ||
|
|
||
| args = iter(command[1:]) | ||
| for arg in args: | ||
| # compiler action parameters are the most important ones... | ||
| if arg in {'-E', '-S', '-cc1', '-M', '-MM', '-###'}: | ||
| result.update({'action': Action.Ignored}) | ||
| elif arg == '-c': | ||
| result.update({'action': max(result['action'], Action.Compile)}) | ||
| # arch flags are taken... | ||
| elif arg == '-arch': | ||
| archs = result.get('archs_seen', []) | ||
| result.update({'archs_seen': archs + [next(args)]}) | ||
| # explicit language option taken... | ||
| elif arg == '-x': | ||
| result.update({'language': next(args)}) | ||
| # output flag taken... | ||
| elif arg == '-o': | ||
| result.update({'output': next(args)}) | ||
| # warning disable options are taken... | ||
| elif re.match(r'^-Wno-', arg): | ||
| result['compile_options'].append(arg) | ||
| # warning options are ignored... | ||
| elif re.match(r'^-[mW].+', arg): | ||
| pass | ||
| # some preprocessor parameters are ignored... | ||
| elif arg in {'-MD', '-MMD', '-MG', '-MP'}: | ||
| pass | ||
| elif arg in {'-MF', '-MT', '-MQ'}: | ||
| next(args) | ||
| # linker options are ignored... | ||
| elif arg in {'-static', '-shared', '-s', '-rdynamic'} or \ | ||
| re.match(r'^-[lL].+', arg): | ||
| pass | ||
| elif arg in {'-l', '-L', '-u', '-z', '-T', '-Xlinker'}: | ||
| next(args) | ||
| # some other options are ignored... | ||
| elif arg in ignored.keys(): | ||
| for _ in range(ignored[arg]): | ||
| next(args) | ||
| # parameters which looks source file are taken... | ||
| elif re.match(r'^[^-].+', arg) and classify_source(arg): | ||
| result['files'].append(arg) | ||
| # and consider everything else as compile option. | ||
| else: | ||
| result['compile_options'].append(arg) | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| def classify_source(filename, cplusplus=False): | ||
| """ Return the language from file name extension. """ | ||
|
|
||
| mapping = { | ||
| '.c': 'c++' if cplusplus else 'c', | ||
| '.i': 'c++-cpp-output' if cplusplus else 'c-cpp-output', | ||
| '.ii': 'c++-cpp-output', | ||
| '.m': 'objective-c', | ||
| '.mi': 'objective-c-cpp-output', | ||
| '.mm': 'objective-c++', | ||
| '.mii': 'objective-c++-cpp-output', | ||
| '.C': 'c++', | ||
| '.cc': 'c++', | ||
| '.CC': 'c++', | ||
| '.cp': 'c++', | ||
| '.cpp': 'c++', | ||
| '.cxx': 'c++', | ||
| '.c++': 'c++', | ||
| '.C++': 'c++', | ||
| '.txx': 'c++' | ||
| } | ||
|
|
||
| __, extension = os.path.splitext(os.path.basename(filename)) | ||
| return mapping.get(extension) | ||
|
|
||
|
|
||
| def is_cplusplus_compiler(name): | ||
| """ Returns true when the compiler name refer to a C++ compiler. """ | ||
|
|
||
| match = re.match(r'^([^/]*/)*(\w*-)*(\w+\+\+)(-(\d+(\.\d+){0,3}))?$', name) | ||
| return False if match is None else True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,359 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
| """ This module is responsible to capture the compiler invocation of any | ||
| build process. The result of that should be a compilation database. | ||
| This implementation is using the LD_PRELOAD or DYLD_INSERT_LIBRARIES | ||
| mechanisms provided by the dynamic linker. The related library is implemented | ||
| in C language and can be found under 'libear' directory. | ||
| The 'libear' library is capturing all child process creation and logging the | ||
| relevant information about it into separate files in a specified directory. | ||
| The parameter of this process is the output directory name, where the report | ||
| files shall be placed. This parameter is passed as an environment variable. | ||
| The module also implements compiler wrappers to intercept the compiler calls. | ||
| The module implements the build command execution and the post-processing of | ||
| the output files, which will condensates into a compilation database. """ | ||
|
|
||
| import sys | ||
| import os | ||
| import os.path | ||
| import re | ||
| import itertools | ||
| import json | ||
| import glob | ||
| import argparse | ||
| import logging | ||
| import subprocess | ||
| from libear import build_libear, TemporaryDirectory | ||
| from libscanbuild import duplicate_check, tempdir, initialize_logging | ||
| from libscanbuild import command_entry_point | ||
| from libscanbuild.command import Action, classify_parameters | ||
| from libscanbuild.shell import encode, decode | ||
|
|
||
| __all__ = ['capture', 'intercept_build_main', 'intercept_build_wrapper'] | ||
|
|
||
| GS = chr(0x1d) | ||
| RS = chr(0x1e) | ||
| US = chr(0x1f) | ||
|
|
||
| COMPILER_WRAPPER_CC = 'intercept-cc' | ||
| COMPILER_WRAPPER_CXX = 'intercept-c++' | ||
|
|
||
|
|
||
| @command_entry_point | ||
| def intercept_build_main(bin_dir): | ||
| """ Entry point for 'intercept-build' command. """ | ||
|
|
||
| parser = create_parser() | ||
| args = parser.parse_args() | ||
|
|
||
| initialize_logging(args.verbose) | ||
| logging.debug('Parsed arguments: %s', args) | ||
|
|
||
| if not args.build: | ||
| parser.print_help() | ||
| return 0 | ||
|
|
||
| return capture(args, bin_dir) | ||
|
|
||
|
|
||
| def capture(args, bin_dir): | ||
| """ The entry point of build command interception. """ | ||
|
|
||
| def post_processing(commands): | ||
| """ To make a compilation database, it needs to filter out commands | ||
| which are not compiler calls. Needs to find the source file name | ||
| from the arguments. And do shell escaping on the command. | ||
| To support incremental builds, it is desired to read elements from | ||
| an existing compilation database from a previous run. These elemets | ||
| shall be merged with the new elements. """ | ||
|
|
||
| # create entries from the current run | ||
| current = itertools.chain.from_iterable( | ||
| # creates a sequence of entry generators from an exec, | ||
| # but filter out non compiler calls before. | ||
| (format_entry(x) for x in commands if is_compiler_call(x))) | ||
| # read entries from previous run | ||
| if 'append' in args and args.append and os.path.exists(args.cdb): | ||
| with open(args.cdb) as handle: | ||
| previous = iter(json.load(handle)) | ||
| else: | ||
| previous = iter([]) | ||
| # filter out duplicate entries from both | ||
| duplicate = duplicate_check(entry_hash) | ||
| return (entry for entry in itertools.chain(previous, current) | ||
| if os.path.exists(entry['file']) and not duplicate(entry)) | ||
|
|
||
| with TemporaryDirectory(prefix='intercept-', dir=tempdir()) as tmp_dir: | ||
| # run the build command | ||
| environment = setup_environment(args, tmp_dir, bin_dir) | ||
| logging.debug('run build in environment: %s', environment) | ||
| exit_code = subprocess.call(args.build, env=environment) | ||
| logging.info('build finished with exit code: %d', exit_code) | ||
| # read the intercepted exec calls | ||
| commands = itertools.chain.from_iterable( | ||
| parse_exec_trace(os.path.join(tmp_dir, filename)) | ||
| for filename in sorted(glob.iglob(os.path.join(tmp_dir, '*.cmd')))) | ||
| # do post processing only if that was requested | ||
| if 'raw_entries' not in args or not args.raw_entries: | ||
| entries = post_processing(commands) | ||
| else: | ||
| entries = commands | ||
| # dump the compilation database | ||
| with open(args.cdb, 'w+') as handle: | ||
| json.dump(list(entries), handle, sort_keys=True, indent=4) | ||
| return exit_code | ||
|
|
||
|
|
||
| def setup_environment(args, destination, bin_dir): | ||
| """ Sets up the environment for the build command. | ||
| It sets the required environment variables and execute the given command. | ||
| The exec calls will be logged by the 'libear' preloaded library or by the | ||
| 'wrapper' programs. """ | ||
|
|
||
| c_compiler = args.cc if 'cc' in args else 'cc' | ||
| cxx_compiler = args.cxx if 'cxx' in args else 'c++' | ||
|
|
||
| libear_path = None if args.override_compiler or is_preload_disabled( | ||
| sys.platform) else build_libear(c_compiler, destination) | ||
|
|
||
| environment = dict(os.environ) | ||
| environment.update({'INTERCEPT_BUILD_TARGET_DIR': destination}) | ||
|
|
||
| if not libear_path: | ||
| logging.debug('intercept gonna use compiler wrappers') | ||
| environment.update({ | ||
| 'CC': os.path.join(bin_dir, COMPILER_WRAPPER_CC), | ||
| 'CXX': os.path.join(bin_dir, COMPILER_WRAPPER_CXX), | ||
| 'INTERCEPT_BUILD_CC': c_compiler, | ||
| 'INTERCEPT_BUILD_CXX': cxx_compiler, | ||
| 'INTERCEPT_BUILD_VERBOSE': 'DEBUG' if args.verbose > 2 else 'INFO' | ||
| }) | ||
| elif sys.platform == 'darwin': | ||
| logging.debug('intercept gonna preload libear on OSX') | ||
| environment.update({ | ||
| 'DYLD_INSERT_LIBRARIES': libear_path, | ||
| 'DYLD_FORCE_FLAT_NAMESPACE': '1' | ||
| }) | ||
| else: | ||
| logging.debug('intercept gonna preload libear on UNIX') | ||
| environment.update({'LD_PRELOAD': libear_path}) | ||
|
|
||
| return environment | ||
|
|
||
|
|
||
| def intercept_build_wrapper(cplusplus): | ||
| """ Entry point for `intercept-cc` and `intercept-c++` compiler wrappers. | ||
| It does generate execution report into target directory. And execute | ||
| the wrapped compilation with the real compiler. The parameters for | ||
| report and execution are from environment variables. | ||
| Those parameters which for 'libear' library can't have meaningful | ||
| values are faked. """ | ||
|
|
||
| # initialize wrapper logging | ||
| logging.basicConfig(format='intercept: %(levelname)s: %(message)s', | ||
| level=os.getenv('INTERCEPT_BUILD_VERBOSE', 'INFO')) | ||
| # write report | ||
| try: | ||
| target_dir = os.getenv('INTERCEPT_BUILD_TARGET_DIR') | ||
| if not target_dir: | ||
| raise UserWarning('exec report target directory not found') | ||
| pid = str(os.getpid()) | ||
| target_file = os.path.join(target_dir, pid + '.cmd') | ||
| logging.debug('writing exec report to: %s', target_file) | ||
| with open(target_file, 'ab') as handler: | ||
| working_dir = os.getcwd() | ||
| command = US.join(sys.argv) + US | ||
| content = RS.join([pid, pid, 'wrapper', working_dir, command]) + GS | ||
| handler.write(content.encode('utf-8')) | ||
| except IOError: | ||
| logging.exception('writing exec report failed') | ||
| except UserWarning as warning: | ||
| logging.warning(warning) | ||
| # execute with real compiler | ||
| compiler = os.getenv('INTERCEPT_BUILD_CXX', 'c++') if cplusplus \ | ||
| else os.getenv('INTERCEPT_BUILD_CC', 'cc') | ||
| compilation = [compiler] + sys.argv[1:] | ||
| logging.debug('execute compiler: %s', compilation) | ||
| return subprocess.call(compilation) | ||
|
|
||
|
|
||
| def parse_exec_trace(filename): | ||
| """ Parse the file generated by the 'libear' preloaded library. | ||
| Given filename points to a file which contains the basic report | ||
| generated by the interception library or wrapper command. A single | ||
| report file _might_ contain multiple process creation info. """ | ||
|
|
||
| logging.debug('parse exec trace file: %s', filename) | ||
| with open(filename, 'r') as handler: | ||
| content = handler.read() | ||
| for group in filter(bool, content.split(GS)): | ||
| records = group.split(RS) | ||
| yield { | ||
| 'pid': records[0], | ||
| 'ppid': records[1], | ||
| 'function': records[2], | ||
| 'directory': records[3], | ||
| 'command': records[4].split(US)[:-1] | ||
| } | ||
|
|
||
|
|
||
| def format_entry(entry): | ||
| """ Generate the desired fields for compilation database entries. """ | ||
|
|
||
| def abspath(cwd, name): | ||
| """ Create normalized absolute path from input filename. """ | ||
| fullname = name if os.path.isabs(name) else os.path.join(cwd, name) | ||
| return os.path.normpath(fullname) | ||
|
|
||
| logging.debug('format this command: %s', entry['command']) | ||
| atoms = classify_parameters(entry['command']) | ||
| if atoms['action'] <= Action.Compile: | ||
| for source in atoms['files']: | ||
| compiler = 'c++' if atoms['c++'] else 'cc' | ||
| flags = atoms['compile_options'] | ||
| flags += ['-o', atoms['output']] if atoms['output'] else [] | ||
| flags += ['-x', atoms['language']] if 'language' in atoms else [] | ||
| flags += [elem | ||
| for arch in atoms.get('archs_seen', []) | ||
| for elem in ['-arch', arch]] | ||
| command = [compiler, '-c'] + flags + [source] | ||
| logging.debug('formated as: %s', command) | ||
| yield { | ||
| 'directory': entry['directory'], | ||
| 'command': encode(command), | ||
| 'file': abspath(entry['directory'], source) | ||
| } | ||
|
|
||
|
|
||
| def is_compiler_call(entry): | ||
| """ A predicate to decide the entry is a compiler call or not. """ | ||
|
|
||
| patterns = [ | ||
| re.compile(r'^([^/]*/)*intercept-c(c|\+\+)$'), | ||
| re.compile(r'^([^/]*/)*c(c|\+\+)$'), | ||
| re.compile(r'^([^/]*/)*([^-]*-)*[mg](cc|\+\+)(-\d+(\.\d+){0,2})?$'), | ||
| re.compile(r'^([^/]*/)*([^-]*-)*clang(\+\+)?(-\d+(\.\d+){0,2})?$'), | ||
| re.compile(r'^([^/]*/)*llvm-g(cc|\+\+)$'), | ||
| ] | ||
| executable = entry['command'][0] | ||
| return any((pattern.match(executable) for pattern in patterns)) | ||
|
|
||
|
|
||
| def is_preload_disabled(platform): | ||
| """ Library-based interposition will fail silently if SIP is enabled, | ||
| so this should be detected. You can detect whether SIP is enabled on | ||
| Darwin by checking whether (1) there is a binary called 'csrutil' in | ||
| the path and, if so, (2) whether the output of executing 'csrutil status' | ||
| contains 'System Integrity Protection status: enabled'. | ||
| Same problem on linux when SELinux is enabled. The status query program | ||
| 'sestatus' and the output when it's enabled 'SELinux status: enabled'. """ | ||
|
|
||
| if platform == 'darwin': | ||
| pattern = re.compile(r'System Integrity Protection status:\s+enabled') | ||
| command = ['csrutil', 'status'] | ||
| elif platform in {'linux', 'linux2'}: | ||
| pattern = re.compile(r'SELinux status:\s+enabled') | ||
| command = ['sestatus'] | ||
| else: | ||
| return False | ||
|
|
||
| try: | ||
| lines = subprocess.check_output(command).decode('utf-8') | ||
| return any((pattern.match(line) for line in lines.splitlines())) | ||
| except: | ||
| return False | ||
|
|
||
|
|
||
| def entry_hash(entry): | ||
| """ Implement unique hash method for compilation database entries. """ | ||
|
|
||
| # For faster lookup in set filename is reverted | ||
| filename = entry['file'][::-1] | ||
| # For faster lookup in set directory is reverted | ||
| directory = entry['directory'][::-1] | ||
| # On OS X the 'cc' and 'c++' compilers are wrappers for | ||
| # 'clang' therefore both call would be logged. To avoid | ||
| # this the hash does not contain the first word of the | ||
| # command. | ||
| command = ' '.join(decode(entry['command'])[1:]) | ||
|
|
||
| return '<>'.join([filename, directory, command]) | ||
|
|
||
|
|
||
| def create_parser(): | ||
| """ Command line argument parser factory method. """ | ||
|
|
||
| parser = argparse.ArgumentParser( | ||
| formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
|
|
||
| parser.add_argument( | ||
| '--verbose', '-v', | ||
| action='count', | ||
| default=0, | ||
| help="""Enable verbose output from '%(prog)s'. A second and third | ||
| flag increases verbosity.""") | ||
| parser.add_argument( | ||
| '--cdb', | ||
| metavar='<file>', | ||
| default="compile_commands.json", | ||
| help="""The JSON compilation database.""") | ||
| group = parser.add_mutually_exclusive_group() | ||
| group.add_argument( | ||
| '--append', | ||
| action='store_true', | ||
| help="""Append new entries to existing compilation database.""") | ||
| group.add_argument( | ||
| '--disable-filter', '-n', | ||
| dest='raw_entries', | ||
| action='store_true', | ||
| help="""Intercepted child process creation calls (exec calls) are all | ||
| logged to the output. The output is not a compilation database. | ||
| This flag is for debug purposes.""") | ||
|
|
||
| advanced = parser.add_argument_group('advanced options') | ||
| advanced.add_argument( | ||
| '--override-compiler', | ||
| action='store_true', | ||
| help="""Always resort to the compiler wrapper even when better | ||
| intercept methods are available.""") | ||
| advanced.add_argument( | ||
| '--use-cc', | ||
| metavar='<path>', | ||
| dest='cc', | ||
| default='cc', | ||
| help="""When '%(prog)s' analyzes a project by interposing a compiler | ||
| wrapper, which executes a real compiler for compilation and | ||
| do other tasks (record the compiler invocation). Because of | ||
| this interposing, '%(prog)s' does not know what compiler your | ||
| project normally uses. Instead, it simply overrides the CC | ||
| environment variable, and guesses your default compiler. | ||
| If you need '%(prog)s' to use a specific compiler for | ||
| *compilation* then you can use this option to specify a path | ||
| to that compiler.""") | ||
| advanced.add_argument( | ||
| '--use-c++', | ||
| metavar='<path>', | ||
| dest='cxx', | ||
| default='c++', | ||
| help="""This is the same as "--use-cc" but for C++ code.""") | ||
|
|
||
| parser.add_argument( | ||
| dest='build', | ||
| nargs=argparse.REMAINDER, | ||
| help="""Command to run.""") | ||
|
|
||
| return parser |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| body { color:#000000; background-color:#ffffff } | ||
| body { font-family: Helvetica, sans-serif; font-size:9pt } | ||
| h1 { font-size: 14pt; } | ||
| h2 { font-size: 12pt; } | ||
| table { font-size:9pt } | ||
| table { border-spacing: 0px; border: 1px solid black } | ||
| th, table thead { | ||
| background-color:#eee; color:#666666; | ||
| font-weight: bold; cursor: default; | ||
| text-align:center; | ||
| font-weight: bold; font-family: Verdana; | ||
| white-space:nowrap; | ||
| } | ||
| .W { font-size:0px } | ||
| th, td { padding:5px; padding-left:8px; text-align:left } | ||
| td.SUMM_DESC { padding-left:12px } | ||
| td.DESC { white-space:pre } | ||
| td.Q { text-align:right } | ||
| td { text-align:left } | ||
| tbody.scrollContent { overflow:auto } | ||
|
|
||
| table.form_group { | ||
| background-color: #ccc; | ||
| border: 1px solid #333; | ||
| padding: 2px; | ||
| } | ||
|
|
||
| table.form_inner_group { | ||
| background-color: #ccc; | ||
| border: 1px solid #333; | ||
| padding: 0px; | ||
| } | ||
|
|
||
| table.form { | ||
| background-color: #999; | ||
| border: 1px solid #333; | ||
| padding: 2px; | ||
| } | ||
|
|
||
| td.form_label { | ||
| text-align: right; | ||
| vertical-align: top; | ||
| } | ||
| /* For one line entires */ | ||
| td.form_clabel { | ||
| text-align: right; | ||
| vertical-align: center; | ||
| } | ||
| td.form_value { | ||
| text-align: left; | ||
| vertical-align: top; | ||
| } | ||
| td.form_submit { | ||
| text-align: right; | ||
| vertical-align: top; | ||
| } | ||
|
|
||
| h1.SubmitFail { | ||
| color: #f00; | ||
| } | ||
| h1.SubmitOk { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| function SetDisplay(RowClass, DisplayVal) | ||
| { | ||
| var Rows = document.getElementsByTagName("tr"); | ||
| for ( var i = 0 ; i < Rows.length; ++i ) { | ||
| if (Rows[i].className == RowClass) { | ||
| Rows[i].style.display = DisplayVal; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function CopyCheckedStateToCheckButtons(SummaryCheckButton) { | ||
| var Inputs = document.getElementsByTagName("input"); | ||
| for ( var i = 0 ; i < Inputs.length; ++i ) { | ||
| if (Inputs[i].type == "checkbox") { | ||
| if(Inputs[i] != SummaryCheckButton) { | ||
| Inputs[i].checked = SummaryCheckButton.checked; | ||
| Inputs[i].onclick(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function returnObjById( id ) { | ||
| if (document.getElementById) | ||
| var returnVar = document.getElementById(id); | ||
| else if (document.all) | ||
| var returnVar = document.all[id]; | ||
| else if (document.layers) | ||
| var returnVar = document.layers[id]; | ||
| return returnVar; | ||
| } | ||
|
|
||
| var NumUnchecked = 0; | ||
|
|
||
| function ToggleDisplay(CheckButton, ClassName) { | ||
| if (CheckButton.checked) { | ||
| SetDisplay(ClassName, ""); | ||
| if (--NumUnchecked == 0) { | ||
| returnObjById("AllBugsCheck").checked = true; | ||
| } | ||
| } | ||
| else { | ||
| SetDisplay(ClassName, "none"); | ||
| NumUnchecked++; | ||
| returnObjById("AllBugsCheck").checked = false; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
| """ This module is responsible to run the analyzer commands. """ | ||
|
|
||
| import os | ||
| import os.path | ||
| import tempfile | ||
| import functools | ||
| import subprocess | ||
| import logging | ||
| from libscanbuild.command import classify_parameters, Action, classify_source | ||
| from libscanbuild.clang import get_arguments, get_version | ||
| from libscanbuild.shell import decode | ||
|
|
||
| __all__ = ['run'] | ||
|
|
||
|
|
||
| def require(required): | ||
| """ Decorator for checking the required values in state. | ||
| It checks the required attributes in the passed state and stop when | ||
| any of those is missing. """ | ||
|
|
||
| def decorator(function): | ||
| @functools.wraps(function) | ||
| def wrapper(*args, **kwargs): | ||
| for key in required: | ||
| if key not in args[0]: | ||
| raise KeyError( | ||
| '{0} not passed to {1}'.format(key, function.__name__)) | ||
|
|
||
| return function(*args, **kwargs) | ||
|
|
||
| return wrapper | ||
|
|
||
| return decorator | ||
|
|
||
|
|
||
| @require(['command', 'directory', 'file', # an entry from compilation database | ||
| 'clang', 'direct_args', # compiler name, and arguments from command | ||
| 'output_dir', 'output_format', 'output_failures']) | ||
| def run(opts): | ||
| """ Entry point to run (or not) static analyzer against a single entry | ||
| of the compilation database. | ||
| This complex task is decomposed into smaller methods which are calling | ||
| each other in chain. If the analyzis is not possibe the given method | ||
| just return and break the chain. | ||
| The passed parameter is a python dictionary. Each method first check | ||
| that the needed parameters received. (This is done by the 'require' | ||
| decorator. It's like an 'assert' to check the contract between the | ||
| caller and the called method.) """ | ||
|
|
||
| try: | ||
| command = opts.pop('command') | ||
| logging.debug("Run analyzer against '%s'", command) | ||
| opts.update(classify_parameters(decode(command))) | ||
|
|
||
| return action_check(opts) | ||
| except Exception: | ||
| logging.error("Problem occured during analyzis.", exc_info=1) | ||
| return None | ||
|
|
||
|
|
||
| @require(['report', 'directory', 'clang', 'output_dir', 'language', 'file', | ||
| 'error_type', 'error_output', 'exit_code']) | ||
| def report_failure(opts): | ||
| """ Create report when analyzer failed. | ||
| The major report is the preprocessor output. The output filename generated | ||
| randomly. The compiler output also captured into '.stderr.txt' file. | ||
| And some more execution context also saved into '.info.txt' file. """ | ||
|
|
||
| def extension(opts): | ||
| """ Generate preprocessor file extension. """ | ||
|
|
||
| mapping = {'objective-c++': '.mii', 'objective-c': '.mi', 'c++': '.ii'} | ||
| return mapping.get(opts['language'], '.i') | ||
|
|
||
| def destination(opts): | ||
| """ Creates failures directory if not exits yet. """ | ||
|
|
||
| name = os.path.join(opts['output_dir'], 'failures') | ||
| if not os.path.isdir(name): | ||
| os.makedirs(name) | ||
| return name | ||
|
|
||
| error = opts['error_type'] | ||
| (handle, name) = tempfile.mkstemp(suffix=extension(opts), | ||
| prefix='clang_' + error + '_', | ||
| dir=destination(opts)) | ||
| os.close(handle) | ||
| cwd = opts['directory'] | ||
| cmd = get_arguments([opts['clang']] + opts['report'] + ['-o', name], cwd) | ||
| logging.debug('exec command in %s: %s', cwd, ' '.join(cmd)) | ||
| subprocess.call(cmd, cwd=cwd) | ||
|
|
||
| with open(name + '.info.txt', 'w') as handle: | ||
| handle.write(opts['file'] + os.linesep) | ||
| handle.write(error.title().replace('_', ' ') + os.linesep) | ||
| handle.write(' '.join(cmd) + os.linesep) | ||
| handle.write(' '.join(os.uname()) + os.linesep) | ||
| handle.write(get_version(cmd[0])) | ||
| handle.close() | ||
|
|
||
| with open(name + '.stderr.txt', 'w') as handle: | ||
| handle.writelines(opts['error_output']) | ||
| handle.close() | ||
|
|
||
| return { | ||
| 'error_output': opts['error_output'], | ||
| 'exit_code': opts['exit_code'] | ||
| } | ||
|
|
||
|
|
||
| @require(['clang', 'analyze', 'directory', 'output']) | ||
| def run_analyzer(opts, continuation=report_failure): | ||
| """ It assembles the analysis command line and executes it. Capture the | ||
| output of the analysis and returns with it. If failure reports are | ||
| requested, it calls the continuation to generate it. """ | ||
|
|
||
| cwd = opts['directory'] | ||
| cmd = get_arguments([opts['clang']] + opts['analyze'] + opts['output'], | ||
| cwd) | ||
| logging.debug('exec command in %s: %s', cwd, ' '.join(cmd)) | ||
| child = subprocess.Popen(cmd, | ||
| cwd=cwd, | ||
| universal_newlines=True, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT) | ||
| output = child.stdout.readlines() | ||
| child.stdout.close() | ||
| # do report details if it were asked | ||
| child.wait() | ||
| if opts.get('output_failures', False) and child.returncode: | ||
| error_type = 'crash' if child.returncode & 127 else 'other_error' | ||
| opts.update({ | ||
| 'error_type': error_type, | ||
| 'error_output': output, | ||
| 'exit_code': child.returncode | ||
| }) | ||
| return continuation(opts) | ||
| return {'error_output': output, 'exit_code': child.returncode} | ||
|
|
||
|
|
||
| @require(['output_dir']) | ||
| def set_analyzer_output(opts, continuation=run_analyzer): | ||
| """ Create output file if was requested. | ||
| This plays a role only if .plist files are requested. """ | ||
|
|
||
| if opts.get('output_format') in {'plist', 'plist-html'}: | ||
| with tempfile.NamedTemporaryFile(prefix='report-', | ||
| suffix='.plist', | ||
| delete=False, | ||
| dir=opts['output_dir']) as output: | ||
| opts.update({'output': ['-o', output.name]}) | ||
| return continuation(opts) | ||
| else: | ||
| opts.update({'output': ['-o', opts['output_dir']]}) | ||
| return continuation(opts) | ||
|
|
||
|
|
||
| @require(['file', 'directory', 'clang', 'direct_args', 'language', | ||
| 'output_dir', 'output_format', 'output_failures']) | ||
| def create_commands(opts, continuation=set_analyzer_output): | ||
| """ Create command to run analyzer or failure report generation. | ||
| It generates commands (from compilation database entries) which contains | ||
| enough information to run the analyzer (and the crash report generation | ||
| if that was requested). """ | ||
|
|
||
| common = [] | ||
| if 'arch' in opts: | ||
| common.extend(['-arch', opts.pop('arch')]) | ||
| common.extend(opts.pop('compile_options', [])) | ||
| common.extend(['-x', opts['language']]) | ||
| common.append(os.path.relpath(opts['file'], opts['directory'])) | ||
|
|
||
| opts.update({ | ||
| 'analyze': ['--analyze'] + opts['direct_args'] + common, | ||
| 'report': ['-fsyntax-only', '-E'] + common | ||
| }) | ||
|
|
||
| return continuation(opts) | ||
|
|
||
|
|
||
| @require(['file', 'c++']) | ||
| def language_check(opts, continuation=create_commands): | ||
| """ Find out the language from command line parameters or file name | ||
| extension. The decision also influenced by the compiler invocation. """ | ||
|
|
||
| accepteds = { | ||
| 'c', 'c++', 'objective-c', 'objective-c++', 'c-cpp-output', | ||
| 'c++-cpp-output', 'objective-c-cpp-output' | ||
| } | ||
|
|
||
| key = 'language' | ||
| language = opts[key] if key in opts else \ | ||
| classify_source(opts['file'], opts['c++']) | ||
|
|
||
| if language is None: | ||
| logging.debug('skip analysis, language not known') | ||
| return None | ||
| elif language not in accepteds: | ||
| logging.debug('skip analysis, language not supported') | ||
| return None | ||
| else: | ||
| logging.debug('analysis, language: %s', language) | ||
| opts.update({key: language}) | ||
| return continuation(opts) | ||
|
|
||
|
|
||
| @require([]) | ||
| def arch_check(opts, continuation=language_check): | ||
| """ Do run analyzer through one of the given architectures. """ | ||
|
|
||
| disableds = {'ppc', 'ppc64'} | ||
|
|
||
| key = 'archs_seen' | ||
| if key in opts: | ||
| # filter out disabled architectures and -arch switches | ||
| archs = [a for a in opts[key] if a not in disableds] | ||
|
|
||
| if not archs: | ||
| logging.debug('skip analysis, found not supported arch') | ||
| return None | ||
| else: | ||
| # There should be only one arch given (or the same multiple | ||
| # times). If there are multiple arch are given and are not | ||
| # the same, those should not change the pre-processing step. | ||
| # But that's the only pass we have before run the analyzer. | ||
| arch = archs.pop() | ||
| logging.debug('analysis, on arch: %s', arch) | ||
|
|
||
| opts.update({'arch': arch}) | ||
| del opts[key] | ||
| return continuation(opts) | ||
| else: | ||
| logging.debug('analysis, on default arch') | ||
| return continuation(opts) | ||
|
|
||
|
|
||
| @require(['action']) | ||
| def action_check(opts, continuation=arch_check): | ||
| """ Continue analysis only if it compilation or link. """ | ||
|
|
||
| if opts.pop('action') <= Action.Compile: | ||
| return continuation(opts) | ||
| else: | ||
| logging.debug('skip analysis, not compilation nor link') | ||
| return None |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
| """ This module implements basic shell escaping/unescaping methods. """ | ||
|
|
||
| import re | ||
| import shlex | ||
|
|
||
| __all__ = ['encode', 'decode'] | ||
|
|
||
|
|
||
| def encode(command): | ||
| """ Takes a command as list and returns a string. """ | ||
|
|
||
| def needs_quote(word): | ||
| """ Returns true if arguments needs to be protected by quotes. | ||
| Previous implementation was shlex.split method, but that's not good | ||
| for this job. Currently is running through the string with a basic | ||
| state checking. """ | ||
|
|
||
| reserved = {' ', '$', '%', '&', '(', ')', '[', ']', '{', '}', '*', '|', | ||
| '<', '>', '@', '?', '!'} | ||
| state = 0 | ||
| for current in word: | ||
| if state == 0 and current in reserved: | ||
| return True | ||
| elif state == 0 and current == '\\': | ||
| state = 1 | ||
| elif state == 1 and current in reserved | {'\\'}: | ||
| state = 0 | ||
| elif state == 0 and current == '"': | ||
| state = 2 | ||
| elif state == 2 and current == '"': | ||
| state = 0 | ||
| elif state == 0 and current == "'": | ||
| state = 3 | ||
| elif state == 3 and current == "'": | ||
| state = 0 | ||
| return state != 0 | ||
|
|
||
| def escape(word): | ||
| """ Do protect argument if that's needed. """ | ||
|
|
||
| table = {'\\': '\\\\', '"': '\\"'} | ||
| escaped = ''.join([table.get(c, c) for c in word]) | ||
|
|
||
| return '"' + escaped + '"' if needs_quote(word) else escaped | ||
|
|
||
| return " ".join([escape(arg) for arg in command]) | ||
|
|
||
|
|
||
| def decode(string): | ||
| """ Takes a command string and returns as a list. """ | ||
|
|
||
| def unescape(arg): | ||
| """ Gets rid of the escaping characters. """ | ||
|
|
||
| if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] == '"': | ||
| arg = arg[1:-1] | ||
| return re.sub(r'\\(["\\])', r'\1', arg) | ||
| return re.sub(r'\\([\\ $%&\(\)\[\]\{\}\*|<>@?!])', r'\1', arg) | ||
|
|
||
| return [unescape(arg) for arg in shlex.split(string)] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| import unittest | ||
|
|
||
| import tests.unit | ||
| import tests.functional.cases | ||
|
|
||
|
|
||
| def suite(): | ||
| loader = unittest.TestLoader() | ||
| suite = unittest.TestSuite() | ||
| suite.addTests(loader.loadTestsFromModule(tests.unit)) | ||
| suite.addTests(loader.loadTestsFromModule(tests.functional.cases)) | ||
| return suite |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| import re | ||
| import os.path | ||
| import subprocess | ||
|
|
||
|
|
||
| def load_tests(loader, suite, pattern): | ||
| from . import test_from_cdb | ||
| suite.addTests(loader.loadTestsFromModule(test_from_cdb)) | ||
| from . import test_from_cmd | ||
| suite.addTests(loader.loadTestsFromModule(test_from_cmd)) | ||
| from . import test_create_cdb | ||
| suite.addTests(loader.loadTestsFromModule(test_create_cdb)) | ||
| from . import test_exec_anatomy | ||
| suite.addTests(loader.loadTestsFromModule(test_exec_anatomy)) | ||
| return suite | ||
|
|
||
|
|
||
| def make_args(target): | ||
| this_dir, _ = os.path.split(__file__) | ||
| path = os.path.normpath(os.path.join(this_dir, '..', 'src')) | ||
| return ['make', 'SRCDIR={}'.format(path), 'OBJDIR={}'.format(target), '-f', | ||
| os.path.join(path, 'build', 'Makefile')] | ||
|
|
||
|
|
||
| def silent_call(cmd, *args, **kwargs): | ||
| kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}) | ||
| return subprocess.call(cmd, *args, **kwargs) | ||
|
|
||
|
|
||
| def silent_check_call(cmd, *args, **kwargs): | ||
| kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}) | ||
| return subprocess.check_call(cmd, *args, **kwargs) | ||
|
|
||
|
|
||
| def call_and_report(analyzer_cmd, build_cmd): | ||
| child = subprocess.Popen(analyzer_cmd + ['-v'] + build_cmd, | ||
| universal_newlines=True, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT) | ||
|
|
||
| pattern = re.compile('Report directory created: (.+)') | ||
| directory = None | ||
| for line in child.stdout.readlines(): | ||
| match = pattern.search(line) | ||
| if match and match.lastindex == 1: | ||
| directory = match.group(1) | ||
| break | ||
| child.stdout.close() | ||
| child.wait() | ||
|
|
||
| return (child.returncode, directory) | ||
|
|
||
|
|
||
| def check_call_and_report(analyzer_cmd, build_cmd): | ||
| exit_code, result = call_and_report(analyzer_cmd, build_cmd) | ||
| if exit_code != 0: | ||
| raise subprocess.CalledProcessError( | ||
| exit_code, analyzer_cmd + build_cmd, None) | ||
| else: | ||
| return result | ||
|
|
||
|
|
||
| def create_empty_file(filename): | ||
| with open(filename, 'a') as handle: | ||
| pass |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| from ...unit import fixtures | ||
| from . import make_args, silent_check_call, silent_call, create_empty_file | ||
| import unittest | ||
|
|
||
| import os.path | ||
| import json | ||
|
|
||
|
|
||
| class CompilationDatabaseTest(unittest.TestCase): | ||
| @staticmethod | ||
| def run_intercept(tmpdir, args): | ||
| result = os.path.join(tmpdir, 'cdb.json') | ||
| make = make_args(tmpdir) + args | ||
| silent_check_call( | ||
| ['intercept-build', '--cdb', result] + make) | ||
| return result | ||
|
|
||
| @staticmethod | ||
| def count_entries(filename): | ||
| with open(filename, 'r') as handler: | ||
| content = json.load(handler) | ||
| return len(content) | ||
|
|
||
| def test_successful_build(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = self.run_intercept(tmpdir, ['build_regular']) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(5, self.count_entries(result)) | ||
|
|
||
| def test_successful_build_with_wrapper(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = os.path.join(tmpdir, 'cdb.json') | ||
| make = make_args(tmpdir) + ['build_regular'] | ||
| silent_check_call(['intercept-build', '--cdb', result, | ||
| '--override-compiler'] + make) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(5, self.count_entries(result)) | ||
|
|
||
| @unittest.skipIf(os.getenv('TRAVIS'), 'ubuntu make return -11') | ||
| def test_successful_build_parallel(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = self.run_intercept(tmpdir, ['-j', '4', 'build_regular']) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(5, self.count_entries(result)) | ||
|
|
||
| @unittest.skipIf(os.getenv('TRAVIS'), 'ubuntu env remove clang from path') | ||
| def test_successful_build_on_empty_env(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = os.path.join(tmpdir, 'cdb.json') | ||
| make = make_args(tmpdir) + ['CC=clang', 'build_regular'] | ||
| silent_check_call(['intercept-build', '--cdb', result, | ||
| 'env', '-'] + make) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(5, self.count_entries(result)) | ||
|
|
||
| def test_successful_build_all_in_one(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = self.run_intercept(tmpdir, ['build_all_in_one']) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(5, self.count_entries(result)) | ||
|
|
||
| def test_not_successful_build(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = os.path.join(tmpdir, 'cdb.json') | ||
| make = make_args(tmpdir) + ['build_broken'] | ||
| silent_call( | ||
| ['intercept-build', '--cdb', result] + make) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(2, self.count_entries(result)) | ||
|
|
||
|
|
||
| class ExitCodeTest(unittest.TestCase): | ||
| @staticmethod | ||
| def run_intercept(tmpdir, target): | ||
| result = os.path.join(tmpdir, 'cdb.json') | ||
| make = make_args(tmpdir) + [target] | ||
| return silent_call( | ||
| ['intercept-build', '--cdb', result] + make) | ||
|
|
||
| def test_successful_build(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| exitcode = self.run_intercept(tmpdir, 'build_clean') | ||
| self.assertFalse(exitcode) | ||
|
|
||
| def test_not_successful_build(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| exitcode = self.run_intercept(tmpdir, 'build_broken') | ||
| self.assertTrue(exitcode) | ||
|
|
||
|
|
||
| class ResumeFeatureTest(unittest.TestCase): | ||
| @staticmethod | ||
| def run_intercept(tmpdir, target, args): | ||
| result = os.path.join(tmpdir, 'cdb.json') | ||
| make = make_args(tmpdir) + [target] | ||
| silent_check_call( | ||
| ['intercept-build', '--cdb', result] + args + make) | ||
| return result | ||
|
|
||
| @staticmethod | ||
| def count_entries(filename): | ||
| with open(filename, 'r') as handler: | ||
| content = json.load(handler) | ||
| return len(content) | ||
|
|
||
| def test_overwrite_existing_cdb(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = self.run_intercept(tmpdir, 'build_clean', []) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| result = self.run_intercept(tmpdir, 'build_regular', []) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(2, self.count_entries(result)) | ||
|
|
||
| def test_append_to_existing_cdb(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| result = self.run_intercept(tmpdir, 'build_clean', []) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| result = self.run_intercept(tmpdir, 'build_regular', ['--append']) | ||
| self.assertTrue(os.path.isfile(result)) | ||
| self.assertEqual(5, self.count_entries(result)) | ||
|
|
||
|
|
||
| class ResultFormatingTest(unittest.TestCase): | ||
| @staticmethod | ||
| def run_intercept(tmpdir, command): | ||
| result = os.path.join(tmpdir, 'cdb.json') | ||
| silent_check_call( | ||
| ['intercept-build', '--cdb', result] + command, | ||
| cwd=tmpdir) | ||
| with open(result, 'r') as handler: | ||
| content = json.load(handler) | ||
| return content | ||
|
|
||
| def assert_creates_number_of_entries(self, command, count): | ||
| with fixtures.TempDir() as tmpdir: | ||
| filename = os.path.join(tmpdir, 'test.c') | ||
| create_empty_file(filename) | ||
| command.append(filename) | ||
| cmd = ['sh', '-c', ' '.join(command)] | ||
| cdb = self.run_intercept(tmpdir, cmd) | ||
| self.assertEqual(count, len(cdb)) | ||
|
|
||
| def test_filter_preprocessor_only_calls(self): | ||
| self.assert_creates_number_of_entries(['cc', '-c'], 1) | ||
| self.assert_creates_number_of_entries(['cc', '-c', '-E'], 0) | ||
| self.assert_creates_number_of_entries(['cc', '-c', '-M'], 0) | ||
| self.assert_creates_number_of_entries(['cc', '-c', '-MM'], 0) | ||
|
|
||
| def assert_command_creates_entry(self, command, expected): | ||
| with fixtures.TempDir() as tmpdir: | ||
| filename = os.path.join(tmpdir, command[-1]) | ||
| create_empty_file(filename) | ||
| cmd = ['sh', '-c', ' '.join(command)] | ||
| cdb = self.run_intercept(tmpdir, cmd) | ||
| self.assertEqual(' '.join(expected), cdb[0]['command']) | ||
|
|
||
| def test_filter_preprocessor_flags(self): | ||
| self.assert_command_creates_entry( | ||
| ['cc', '-c', '-MD', 'test.c'], | ||
| ['cc', '-c', 'test.c']) | ||
| self.assert_command_creates_entry( | ||
| ['cc', '-c', '-MMD', 'test.c'], | ||
| ['cc', '-c', 'test.c']) | ||
| self.assert_command_creates_entry( | ||
| ['cc', '-c', '-MD', '-MF', 'test.d', 'test.c'], | ||
| ['cc', '-c', 'test.c']) | ||
|
|
||
| def test_pass_language_flag(self): | ||
| self.assert_command_creates_entry( | ||
| ['cc', '-c', '-x', 'c', 'test.c'], | ||
| ['cc', '-c', '-x', 'c', 'test.c']) | ||
| self.assert_command_creates_entry( | ||
| ['cc', '-c', 'test.c'], | ||
| ['cc', '-c', 'test.c']) | ||
|
|
||
| def test_pass_arch_flags(self): | ||
| self.assert_command_creates_entry( | ||
| ['clang', '-c', 'test.c'], | ||
| ['cc', '-c', 'test.c']) | ||
| self.assert_command_creates_entry( | ||
| ['clang', '-c', '-arch', 'i386', 'test.c'], | ||
| ['cc', '-c', '-arch', 'i386', 'test.c']) | ||
| self.assert_command_creates_entry( | ||
| ['clang', '-c', '-arch', 'i386', '-arch', 'armv7l', 'test.c'], | ||
| ['cc', '-c', '-arch', 'i386', '-arch', 'armv7l', 'test.c']) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| from ...unit import fixtures | ||
| import unittest | ||
|
|
||
| import os.path | ||
| import subprocess | ||
| import json | ||
|
|
||
|
|
||
| def run(source_dir, target_dir): | ||
| def execute(cmd): | ||
| return subprocess.check_call(cmd, | ||
| cwd=target_dir, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT) | ||
|
|
||
| execute(['cmake', source_dir]) | ||
| execute(['make']) | ||
|
|
||
| result_file = os.path.join(target_dir, 'result.json') | ||
| expected_file = os.path.join(target_dir, 'expected.json') | ||
| execute(['intercept-build', '--cdb', result_file, './exec', | ||
| expected_file]) | ||
| return (expected_file, result_file) | ||
|
|
||
|
|
||
| class ExecAnatomyTest(unittest.TestCase): | ||
| def assertEqualJson(self, expected, result): | ||
| def read_json(filename): | ||
| with open(filename) as handler: | ||
| return json.load(handler) | ||
|
|
||
| lhs = read_json(expected) | ||
| rhs = read_json(result) | ||
| for item in lhs: | ||
| self.assertTrue(rhs.count(item)) | ||
| for item in rhs: | ||
| self.assertTrue(lhs.count(item)) | ||
|
|
||
| def test_all_exec_calls(self): | ||
| this_dir, _ = os.path.split(__file__) | ||
| source_dir = os.path.normpath(os.path.join(this_dir, '..', 'exec')) | ||
| with fixtures.TempDir() as tmp_dir: | ||
| expected, result = run(source_dir, tmp_dir) | ||
| self.assertEqualJson(expected, result) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| from ...unit import fixtures | ||
| from . import call_and_report | ||
| import unittest | ||
|
|
||
| import os.path | ||
| import string | ||
| import subprocess | ||
| import glob | ||
|
|
||
|
|
||
| def prepare_cdb(name, target_dir): | ||
| target_file = 'build_{0}.json'.format(name) | ||
| this_dir, _ = os.path.split(__file__) | ||
| path = os.path.normpath(os.path.join(this_dir, '..', 'src')) | ||
| source_dir = os.path.join(path, 'compilation_database') | ||
| source_file = os.path.join(source_dir, target_file + '.in') | ||
| target_file = os.path.join(target_dir, 'compile_commands.json') | ||
| with open(source_file, 'r') as in_handle: | ||
| with open(target_file, 'w') as out_handle: | ||
| for line in in_handle: | ||
| temp = string.Template(line) | ||
| out_handle.write(temp.substitute(path=path)) | ||
| return target_file | ||
|
|
||
|
|
||
| def run_analyzer(directory, cdb, args): | ||
| cmd = ['analyze-build', '--cdb', cdb, '--output', directory] \ | ||
| + args | ||
| return call_and_report(cmd, []) | ||
|
|
||
|
|
||
| class OutputDirectoryTest(unittest.TestCase): | ||
| def test_regular_keeps_report_dir(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('regular', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, []) | ||
| self.assertTrue(os.path.isdir(reportdir)) | ||
|
|
||
| def test_clear_deletes_report_dir(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('clean', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, []) | ||
| self.assertFalse(os.path.isdir(reportdir)) | ||
|
|
||
| def test_clear_keeps_report_dir_when_asked(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('clean', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, ['--keep-empty']) | ||
| self.assertTrue(os.path.isdir(reportdir)) | ||
|
|
||
|
|
||
| class ExitCodeTest(unittest.TestCase): | ||
| def test_regular_does_not_set_exit_code(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('regular', tmpdir) | ||
| exit_code, __ = run_analyzer(tmpdir, cdb, []) | ||
| self.assertFalse(exit_code) | ||
|
|
||
| def test_clear_does_not_set_exit_code(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('clean', tmpdir) | ||
| exit_code, __ = run_analyzer(tmpdir, cdb, []) | ||
| self.assertFalse(exit_code) | ||
|
|
||
| def test_regular_sets_exit_code_if_asked(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('regular', tmpdir) | ||
| exit_code, __ = run_analyzer(tmpdir, cdb, ['--status-bugs']) | ||
| self.assertTrue(exit_code) | ||
|
|
||
| def test_clear_does_not_set_exit_code_if_asked(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('clean', tmpdir) | ||
| exit_code, __ = run_analyzer(tmpdir, cdb, ['--status-bugs']) | ||
| self.assertFalse(exit_code) | ||
|
|
||
| def test_regular_sets_exit_code_if_asked_from_plist(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('regular', tmpdir) | ||
| exit_code, __ = run_analyzer( | ||
| tmpdir, cdb, ['--status-bugs', '--plist']) | ||
| self.assertTrue(exit_code) | ||
|
|
||
| def test_clear_does_not_set_exit_code_if_asked_from_plist(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('clean', tmpdir) | ||
| exit_code, __ = run_analyzer( | ||
| tmpdir, cdb, ['--status-bugs', '--plist']) | ||
| self.assertFalse(exit_code) | ||
|
|
||
|
|
||
| class OutputFormatTest(unittest.TestCase): | ||
| @staticmethod | ||
| def get_html_count(directory): | ||
| return len(glob.glob(os.path.join(directory, 'report-*.html'))) | ||
|
|
||
| @staticmethod | ||
| def get_plist_count(directory): | ||
| return len(glob.glob(os.path.join(directory, 'report-*.plist'))) | ||
|
|
||
| def test_default_creates_html_report(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('regular', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, []) | ||
| self.assertTrue( | ||
| os.path.exists(os.path.join(reportdir, 'index.html'))) | ||
| self.assertEqual(self.get_html_count(reportdir), 2) | ||
| self.assertEqual(self.get_plist_count(reportdir), 0) | ||
|
|
||
| def test_plist_and_html_creates_html_report(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('regular', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, ['--plist-html']) | ||
| self.assertTrue( | ||
| os.path.exists(os.path.join(reportdir, 'index.html'))) | ||
| self.assertEqual(self.get_html_count(reportdir), 2) | ||
| self.assertEqual(self.get_plist_count(reportdir), 5) | ||
|
|
||
| def test_plist_does_not_creates_html_report(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('regular', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, ['--plist']) | ||
| self.assertFalse( | ||
| os.path.exists(os.path.join(reportdir, 'index.html'))) | ||
| self.assertEqual(self.get_html_count(reportdir), 0) | ||
| self.assertEqual(self.get_plist_count(reportdir), 5) | ||
|
|
||
|
|
||
| class FailureReportTest(unittest.TestCase): | ||
| def test_broken_creates_failure_reports(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('broken', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, []) | ||
| self.assertTrue( | ||
| os.path.isdir(os.path.join(reportdir, 'failures'))) | ||
|
|
||
| def test_broken_does_not_creates_failure_reports(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('broken', tmpdir) | ||
| exit_code, reportdir = run_analyzer( | ||
| tmpdir, cdb, ['--no-failure-reports']) | ||
| self.assertFalse( | ||
| os.path.isdir(os.path.join(reportdir, 'failures'))) | ||
|
|
||
|
|
||
| class TitleTest(unittest.TestCase): | ||
| def assertTitleEqual(self, directory, expected): | ||
| import re | ||
| patterns = [ | ||
| re.compile(r'<title>(?P<page>.*)</title>'), | ||
| re.compile(r'<h1>(?P<head>.*)</h1>') | ||
| ] | ||
| result = dict() | ||
|
|
||
| index = os.path.join(directory, 'index.html') | ||
| with open(index, 'r') as handler: | ||
| for line in handler.readlines(): | ||
| for regex in patterns: | ||
| match = regex.match(line.strip()) | ||
| if match: | ||
| result.update(match.groupdict()) | ||
| break | ||
| self.assertEqual(result['page'], result['head']) | ||
| self.assertEqual(result['page'], expected) | ||
|
|
||
| def test_default_title_in_report(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('broken', tmpdir) | ||
| exit_code, reportdir = run_analyzer(tmpdir, cdb, []) | ||
| self.assertTitleEqual(reportdir, 'src - analyzer results') | ||
|
|
||
| def test_given_title_in_report(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| cdb = prepare_cdb('broken', tmpdir) | ||
| exit_code, reportdir = run_analyzer( | ||
| tmpdir, cdb, ['--html-title', 'this is the title']) | ||
| self.assertTitleEqual(reportdir, 'this is the title') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| from ...unit import fixtures | ||
| from . import make_args, check_call_and_report, create_empty_file | ||
| import unittest | ||
|
|
||
| import os | ||
| import os.path | ||
| import glob | ||
|
|
||
|
|
||
| class OutputDirectoryTest(unittest.TestCase): | ||
|
|
||
| @staticmethod | ||
| def run_analyzer(outdir, args, cmd): | ||
| return check_call_and_report( | ||
| ['scan-build', '--intercept-first', '-o', outdir] + args, | ||
| cmd) | ||
|
|
||
| def test_regular_keeps_report_dir(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| make = make_args(tmpdir) + ['build_regular'] | ||
| outdir = self.run_analyzer(tmpdir, [], make) | ||
| self.assertTrue(os.path.isdir(outdir)) | ||
|
|
||
| def test_clear_deletes_report_dir(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| make = make_args(tmpdir) + ['build_clean'] | ||
| outdir = self.run_analyzer(tmpdir, [], make) | ||
| self.assertFalse(os.path.isdir(outdir)) | ||
|
|
||
| def test_clear_keeps_report_dir_when_asked(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| make = make_args(tmpdir) + ['build_clean'] | ||
| outdir = self.run_analyzer(tmpdir, ['--keep-empty'], make) | ||
| self.assertTrue(os.path.isdir(outdir)) | ||
|
|
||
|
|
||
| class RunAnalyzerTest(unittest.TestCase): | ||
|
|
||
| @staticmethod | ||
| def get_plist_count(directory): | ||
| return len(glob.glob(os.path.join(directory, 'report-*.plist'))) | ||
|
|
||
| def test_interposition_works(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| make = make_args(tmpdir) + ['build_regular'] | ||
| outdir = check_call_and_report( | ||
| ['scan-build', '--plist', '-o', tmpdir, '--override-compiler'], | ||
| make) | ||
|
|
||
| self.assertTrue(os.path.isdir(outdir)) | ||
| self.assertEqual(self.get_plist_count(outdir), 5) | ||
|
|
||
| def test_intercept_wrapper_works(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| make = make_args(tmpdir) + ['build_regular'] | ||
| outdir = check_call_and_report( | ||
| ['scan-build', '--plist', '-o', tmpdir, '--intercept-first', | ||
| '--override-compiler'], | ||
| make) | ||
|
|
||
| self.assertTrue(os.path.isdir(outdir)) | ||
| self.assertEqual(self.get_plist_count(outdir), 5) | ||
|
|
||
| def test_intercept_library_works(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| make = make_args(tmpdir) + ['build_regular'] | ||
| outdir = check_call_and_report( | ||
| ['scan-build', '--plist', '-o', tmpdir, '--intercept-first'], | ||
| make) | ||
|
|
||
| self.assertTrue(os.path.isdir(outdir)) | ||
| self.assertEqual(self.get_plist_count(outdir), 5) | ||
|
|
||
| @staticmethod | ||
| def compile_empty_source_file(target_dir, is_cxx): | ||
| compiler = '$CXX' if is_cxx else '$CC' | ||
| src_file_name = 'test.cxx' if is_cxx else 'test.c' | ||
| src_file = os.path.join(target_dir, src_file_name) | ||
| obj_file = os.path.join(target_dir, 'test.o') | ||
| create_empty_file(src_file) | ||
| command = ' '.join([compiler, '-c', src_file, '-o', obj_file]) | ||
| return ['sh', '-c', command] | ||
|
|
||
| def test_interposition_cc_works(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| outdir = check_call_and_report( | ||
| ['scan-build', '--plist', '-o', tmpdir, '--override-compiler'], | ||
| self.compile_empty_source_file(tmpdir, False)) | ||
| self.assertEqual(self.get_plist_count(outdir), 1) | ||
|
|
||
| def test_interposition_cxx_works(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| outdir = check_call_and_report( | ||
| ['scan-build', '--plist', '-o', tmpdir, '--override-compiler'], | ||
| self.compile_empty_source_file(tmpdir, True)) | ||
| self.assertEqual(self.get_plist_count(outdir), 1) | ||
|
|
||
| def test_intercept_cc_works(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| outdir = check_call_and_report( | ||
| ['scan-build', '--plist', '-o', tmpdir, '--override-compiler', | ||
| '--intercept-first'], | ||
| self.compile_empty_source_file(tmpdir, False)) | ||
| self.assertEqual(self.get_plist_count(outdir), 1) | ||
|
|
||
| def test_intercept_cxx_works(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| outdir = check_call_and_report( | ||
| ['scan-build', '--plist', '-o', tmpdir, '--override-compiler', | ||
| '--intercept-first'], | ||
| self.compile_empty_source_file(tmpdir, True)) | ||
| self.assertEqual(self.get_plist_count(outdir), 1) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| project(exec C) | ||
|
|
||
| cmake_minimum_required(VERSION 2.8) | ||
|
|
||
| include(CheckCCompilerFlag) | ||
| check_c_compiler_flag("-std=c99" C99_SUPPORTED) | ||
| if (C99_SUPPORTED) | ||
| set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") | ||
| endif() | ||
|
|
||
| include(CheckFunctionExists) | ||
| include(CheckSymbolExists) | ||
|
|
||
| add_definitions(-D_GNU_SOURCE) | ||
| list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) | ||
|
|
||
| check_function_exists(execve HAVE_EXECVE) | ||
| check_function_exists(execv HAVE_EXECV) | ||
| check_function_exists(execvpe HAVE_EXECVPE) | ||
| check_function_exists(execvp HAVE_EXECVP) | ||
| check_function_exists(execvP HAVE_EXECVP2) | ||
| check_function_exists(exect HAVE_EXECT) | ||
| check_function_exists(execl HAVE_EXECL) | ||
| check_function_exists(execlp HAVE_EXECLP) | ||
| check_function_exists(execle HAVE_EXECLE) | ||
| check_function_exists(posix_spawn HAVE_POSIX_SPAWN) | ||
| check_function_exists(posix_spawnp HAVE_POSIX_SPAWNP) | ||
|
|
||
| configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) | ||
| include_directories(${CMAKE_CURRENT_BINARY_DIR}) | ||
|
|
||
| add_executable(exec main.c) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /* -*- coding: utf-8 -*- | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #cmakedefine HAVE_EXECVE | ||
| #cmakedefine HAVE_EXECV | ||
| #cmakedefine HAVE_EXECVPE | ||
| #cmakedefine HAVE_EXECVP | ||
| #cmakedefine HAVE_EXECVP2 | ||
| #cmakedefine HAVE_EXECT | ||
| #cmakedefine HAVE_EXECL | ||
| #cmakedefine HAVE_EXECLP | ||
| #cmakedefine HAVE_EXECLE | ||
| #cmakedefine HAVE_POSIX_SPAWN | ||
| #cmakedefine HAVE_POSIX_SPAWNP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,307 @@ | ||
| /* -*- coding: utf-8 -*- | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| */ | ||
|
|
||
| #include "config.h" | ||
|
|
||
| #include <sys/wait.h> | ||
| #include <unistd.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <paths.h> | ||
|
|
||
| #if defined HAVE_POSIX_SPAWN || defined HAVE_POSIX_SPAWNP | ||
| #include <spawn.h> | ||
| #endif | ||
|
|
||
| // ..:: environment access fixer - begin ::.. | ||
| #ifdef HAVE_NSGETENVIRON | ||
| #include <crt_externs.h> | ||
| #else | ||
| extern char **environ; | ||
| #endif | ||
|
|
||
| char **get_environ() { | ||
| #ifdef HAVE_NSGETENVIRON | ||
| return *_NSGetEnviron(); | ||
| #else | ||
| return environ; | ||
| #endif | ||
| } | ||
| // ..:: environment access fixer - end ::.. | ||
|
|
||
| // ..:: test fixtures - begin ::.. | ||
| static char const *cwd = NULL; | ||
| static FILE *fd = NULL; | ||
| static int need_comma = 0; | ||
|
|
||
| void expected_out_open(const char *expected) { | ||
| cwd = getcwd(NULL, 0); | ||
| fd = fopen(expected, "w"); | ||
| if (!fd) { | ||
| perror("fopen"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| fprintf(fd, "[\n"); | ||
| need_comma = 0; | ||
| } | ||
|
|
||
| void expected_out_close() { | ||
| fprintf(fd, "]\n"); | ||
| fclose(fd); | ||
| fd = NULL; | ||
|
|
||
| free((void *)cwd); | ||
| cwd = NULL; | ||
| } | ||
|
|
||
| void expected_out(const char *file) { | ||
| if (need_comma) | ||
| fprintf(fd, ",\n"); | ||
| else | ||
| need_comma = 1; | ||
|
|
||
| fprintf(fd, "{\n"); | ||
| fprintf(fd, " \"directory\": \"%s\",\n", cwd); | ||
| fprintf(fd, " \"command\": \"cc -c %s\",\n", file); | ||
| fprintf(fd, " \"file\": \"%s/%s\"\n", cwd, file); | ||
| fprintf(fd, "}\n"); | ||
| } | ||
|
|
||
| void create_source(char *file) { | ||
| FILE *fd = fopen(file, "w"); | ||
| if (!fd) { | ||
| perror("fopen"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| fprintf(fd, "typedef int score;\n"); | ||
| fclose(fd); | ||
| } | ||
|
|
||
| typedef void (*exec_fun)(); | ||
|
|
||
| void wait_for(pid_t child) { | ||
| int status; | ||
| if (-1 == waitpid(child, &status, 0)) { | ||
| perror("wait"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| if (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE) { | ||
| fprintf(stderr, "children process has non zero exit code\n"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| } | ||
|
|
||
| #define FORK(FUNC) \ | ||
| { \ | ||
| pid_t child = fork(); \ | ||
| if (-1 == child) { \ | ||
| perror("fork"); \ | ||
| exit(EXIT_FAILURE); \ | ||
| } else if (0 == child) { \ | ||
| FUNC fprintf(stderr, "children process failed to exec\n"); \ | ||
| exit(EXIT_FAILURE); \ | ||
| } else { \ | ||
| wait_for(child); \ | ||
| } \ | ||
| } | ||
| // ..:: test fixtures - end ::.. | ||
|
|
||
| #ifdef HAVE_EXECV | ||
| void call_execv() { | ||
| char *const file = "execv.c"; | ||
| char *const compiler = "/usr/bin/cc"; | ||
| char *const argv[] = {"cc", "-c", file, 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execv(compiler, argv);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECVE | ||
| void call_execve() { | ||
| char *const file = "execve.c"; | ||
| char *const compiler = "/usr/bin/cc"; | ||
| char *const argv[] = {compiler, "-c", file, 0}; | ||
| char *const envp[] = {"THIS=THAT", 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execve(compiler, argv, envp);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECVP | ||
| void call_execvp() { | ||
| char *const file = "execvp.c"; | ||
| char *const compiler = "cc"; | ||
| char *const argv[] = {compiler, "-c", file, 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execvp(compiler, argv);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECVP2 | ||
| void call_execvP() { | ||
| char *const file = "execv_p.c"; | ||
| char *const compiler = "cc"; | ||
| char *const argv[] = {compiler, "-c", file, 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execvP(compiler, _PATH_DEFPATH, argv);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECVPE | ||
| void call_execvpe() { | ||
| char *const file = "execvpe.c"; | ||
| char *const compiler = "cc"; | ||
| char *const argv[] = {"/usr/bin/cc", "-c", file, 0}; | ||
| char *const envp[] = {"THIS=THAT", 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execvpe(compiler, argv, envp);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECT | ||
| void call_exect() { | ||
| char *const file = "exect.c"; | ||
| char *const compiler = "/usr/bin/cc"; | ||
| char *const argv[] = {compiler, "-c", file, 0}; | ||
| char *const envp[] = {"THIS=THAT", 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(exect(compiler, argv, envp);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECL | ||
| void call_execl() { | ||
| char *const file = "execl.c"; | ||
| char *const compiler = "/usr/bin/cc"; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execl(compiler, "cc", "-c", file, (char *)0);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECLP | ||
| void call_execlp() { | ||
| char *const file = "execlp.c"; | ||
| char *const compiler = "cc"; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execlp(compiler, compiler, "-c", file, (char *)0);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_EXECLE | ||
| void call_execle() { | ||
| char *const file = "execle.c"; | ||
| char *const compiler = "/usr/bin/cc"; | ||
| char *const envp[] = {"THIS=THAT", 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| FORK(execle(compiler, compiler, "-c", file, (char *)0, envp);) | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_POSIX_SPAWN | ||
| void call_posix_spawn() { | ||
| char *const file = "posix_spawn.c"; | ||
| char *const compiler = "cc"; | ||
| char *const argv[] = {compiler, "-c", file, 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| pid_t child; | ||
| if (0 != posix_spawn(&child, "/usr/bin/cc", 0, 0, argv, get_environ())) { | ||
| perror("posix_spawn"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| wait_for(child); | ||
| } | ||
| #endif | ||
|
|
||
| #ifdef HAVE_POSIX_SPAWNP | ||
| void call_posix_spawnp() { | ||
| char *const file = "posix_spawnp.c"; | ||
| char *const compiler = "cc"; | ||
| char *const argv[] = {compiler, "-c", file, 0}; | ||
|
|
||
| expected_out(file); | ||
| create_source(file); | ||
|
|
||
| pid_t child; | ||
| if (0 != posix_spawnp(&child, "cc", 0, 0, argv, get_environ())) { | ||
| perror("posix_spawnp"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| wait_for(child); | ||
| } | ||
| #endif | ||
|
|
||
| int main(int argc, char *const argv[]) { | ||
| if (argc != 2) | ||
| exit(EXIT_FAILURE); | ||
|
|
||
| expected_out_open(argv[1]); | ||
| #ifdef HAVE_EXECV | ||
| call_execv(); | ||
| #endif | ||
| #ifdef HAVE_EXECVE | ||
| call_execve(); | ||
| #endif | ||
| #ifdef HAVE_EXECVP | ||
| call_execvp(); | ||
| #endif | ||
| #ifdef HAVE_EXECVP2 | ||
| call_execvP(); | ||
| #endif | ||
| #ifdef HAVE_EXECVPE | ||
| call_execvpe(); | ||
| #endif | ||
| #ifdef HAVE_EXECT | ||
| call_exect(); | ||
| #endif | ||
| #ifdef HAVE_EXECL | ||
| call_execl(); | ||
| #endif | ||
| #ifdef HAVE_EXECLP | ||
| call_execlp(); | ||
| #endif | ||
| #ifdef HAVE_EXECLE | ||
| call_execle(); | ||
| #endif | ||
| #ifdef HAVE_POSIX_SPAWN | ||
| call_posix_spawn(); | ||
| #endif | ||
| #ifdef HAVE_POSIX_SPAWNP | ||
| call_posix_spawnp(); | ||
| #endif | ||
| expected_out_close(); | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #include <notexisting.hpp> | ||
|
|
||
| int value(int in) | ||
| { | ||
| return 2 * in; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| int test() { ; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| SRCDIR := .. | ||
| OBJDIR := . | ||
|
|
||
| CFLAGS = -Wall -DDEBUG -Dvariable="value with space" -I $(SRCDIR)/include | ||
| LDFLAGS = | ||
| PROGRAM = $(OBJDIR)/prg | ||
|
|
||
| $(OBJDIR)/main.o: $(SRCDIR)/main.c | ||
| $(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/main.c | ||
|
|
||
| $(OBJDIR)/clean-one.o: $(SRCDIR)/clean-one.c | ||
| $(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/clean-one.c | ||
|
|
||
| $(OBJDIR)/clean-two.o: $(SRCDIR)/clean-two.c | ||
| $(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/clean-two.c | ||
|
|
||
| $(OBJDIR)/emit-one.o: $(SRCDIR)/emit-one.c | ||
| $(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/emit-one.c | ||
|
|
||
| $(OBJDIR)/emit-two.o: $(SRCDIR)/emit-two.c | ||
| $(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/emit-two.c | ||
|
|
||
| $(OBJDIR)/broken-one.o: $(SRCDIR)/broken-one.c | ||
| $(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/broken-one.c | ||
|
|
||
| $(OBJDIR)/broken-two.o: $(SRCDIR)/broken-two.c | ||
| $(CC) $(CFLAGS) -c -o $@ $(SRCDIR)/broken-two.c | ||
|
|
||
| $(PROGRAM): $(OBJDIR)/main.o $(OBJDIR)/clean-one.o $(OBJDIR)/clean-two.o $(OBJDIR)/emit-one.o $(OBJDIR)/emit-two.o | ||
| $(CC) $(LDFLAGS) -o $@ $(OBJDIR)/main.o $(OBJDIR)/clean-one.o $(OBJDIR)/clean-two.o $(OBJDIR)/emit-one.o $(OBJDIR)/emit-two.o | ||
|
|
||
| build_regular: $(PROGRAM) | ||
|
|
||
| build_clean: $(OBJDIR)/main.o $(OBJDIR)/clean-one.o $(OBJDIR)/clean-two.o | ||
|
|
||
| build_broken: $(OBJDIR)/main.o $(OBJDIR)/broken-one.o $(OBJDIR)/broken-two.o | ||
|
|
||
| build_all_in_one: $(SRCDIR)/main.c $(SRCDIR)/clean-one.c $(SRCDIR)/clean-two.c $(SRCDIR)/emit-one.c $(SRCDIR)/emit-two.c | ||
| $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROGRAM) $(SRCDIR)/main.c $(SRCDIR)/clean-one.c $(SRCDIR)/clean-two.c $(SRCDIR)/emit-one.c $(SRCDIR)/emit-two.c | ||
|
|
||
| clean: | ||
| rm -f $(PROGRAM) $(OBJDIR)/*.o |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| #include <clean-one.h> | ||
|
|
||
| int do_nothing_loop() | ||
| { | ||
| int i = 32; | ||
| int idx = 0; | ||
|
|
||
| for (idx = i; idx > 0; --idx) | ||
| { | ||
| i += idx; | ||
| } | ||
| return i; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #include <clean-one.h> | ||
|
|
||
| #include <stdlib.h> | ||
|
|
||
| unsigned int another_method() | ||
| { | ||
| unsigned int const size = do_nothing_loop(); | ||
| unsigned int const square = size * size; | ||
|
|
||
| return square; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| [ | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o main.o main.c -Wall -DDEBUG -Dvariable=value", | ||
| "file": "${path}/main.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "cc -c -o broken-one.o broken-one.c -Wall -DDEBUG \"-Dvariable=value with space\"", | ||
| "file": "${path}/broken-one.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o broken-two.o broken-two.c -Wall -DDEBUG -Dvariable=value", | ||
| "file": "${path}/broken-two.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "cc -c -o clean-one.o clean-one.c -Wall -DDEBUG \"-Dvariable=value with space\" -Iinclude", | ||
| "file": "${path}/clean-one.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o clean-two.o clean-two.c -Wall -DDEBUG -Dvariable=value -I ./include", | ||
| "file": "${path}/clean-two.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "cc -c -o emit-one.o emit-one.c -Wall -DDEBUG \"-Dvariable=value with space\"", | ||
| "file": "${path}/emit-one.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o emit-two.o emit-two.c -Wall -DDEBUG -Dvariable=value", | ||
| "file": "${path}/emit-two.c" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [ | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o main.o main.c -Wall -DDEBUG -Dvariable=value", | ||
| "file": "${path}/main.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "cc -c -o clean-one.o clean-one.c -Wall -DDEBUG \"-Dvariable=value with space\" -Iinclude", | ||
| "file": "${path}/clean-one.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o clean-two.o clean-two.c -Wall -DDEBUG -Dvariable=value -I ./include", | ||
| "file": "${path}/clean-two.c" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| [ | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o main.o main.c -Wall -DDEBUG -Dvariable=value", | ||
| "file": "${path}/main.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "cc -c -o clean-one.o clean-one.c -Wall -DDEBUG \"-Dvariable=value with space\" -Iinclude", | ||
| "file": "${path}/clean-one.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o clean-two.o clean-two.c -Wall -DDEBUG -Dvariable=value -I ./include", | ||
| "file": "${path}/clean-two.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "cc -c -o emit-one.o emit-one.c -Wall -DDEBUG \"-Dvariable=value with space\"", | ||
| "file": "${path}/emit-one.c" | ||
| } | ||
| , | ||
| { | ||
| "directory": "${path}", | ||
| "command": "g++ -c -o emit-two.o emit-two.c -Wall -DDEBUG -Dvariable=value", | ||
| "file": "${path}/emit-two.c" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| #include <assert.h> | ||
|
|
||
| int div(int numerator, int denominator) | ||
| { | ||
| return numerator / denominator; | ||
| } | ||
|
|
||
| void div_test() | ||
| { | ||
| int i = 0; | ||
| for (i = 0; i < 2; ++i) | ||
| assert(div(2 * i, i) == 2); | ||
| } | ||
|
|
||
| int do_nothing() | ||
| { | ||
| unsigned int i = 0; | ||
|
|
||
| int k = 100; | ||
| int j = k + 1; | ||
|
|
||
| return j; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
|
|
||
| int bad_guy(int * i) | ||
| { | ||
| *i = 9; | ||
| return *i; | ||
| } | ||
|
|
||
| void bad_guy_test() | ||
| { | ||
| int * ptr = 0; | ||
|
|
||
| bad_guy(ptr); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #ifndef CLEAN_ONE_H | ||
| #define CLEAN_ONE_H | ||
|
|
||
| int do_nothing_loop(); | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| int main() | ||
| { | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| from . import test_command | ||
| from . import test_clang | ||
| from . import test_runner | ||
| from . import test_report | ||
| from . import test_analyze | ||
| from . import test_intercept | ||
| from . import test_shell | ||
|
|
||
|
|
||
| def load_tests(loader, suite, pattern): | ||
| suite.addTests(loader.loadTestsFromModule(test_command)) | ||
| suite.addTests(loader.loadTestsFromModule(test_clang)) | ||
| suite.addTests(loader.loadTestsFromModule(test_runner)) | ||
| suite.addTests(loader.loadTestsFromModule(test_report)) | ||
| suite.addTests(loader.loadTestsFromModule(test_analyze)) | ||
| suite.addTests(loader.loadTestsFromModule(test_intercept)) | ||
| suite.addTests(loader.loadTestsFromModule(test_shell)) | ||
| return suite |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| import contextlib | ||
| import tempfile | ||
| import shutil | ||
| import unittest | ||
|
|
||
|
|
||
| class Spy(object): | ||
| def __init__(self): | ||
| self.arg = None | ||
| self.success = 0 | ||
|
|
||
| def call(self, params): | ||
| self.arg = params | ||
| return self.success | ||
|
|
||
|
|
||
| @contextlib.contextmanager | ||
| def TempDir(): | ||
| name = tempfile.mkdtemp(prefix='scan-build-test-') | ||
| try: | ||
| yield name | ||
| finally: | ||
| shutil.rmtree(name) | ||
|
|
||
|
|
||
| class TestCase(unittest.TestCase): | ||
| def assertIn(self, element, collection): | ||
| found = False | ||
| for it in collection: | ||
| if element == it: | ||
| found = True | ||
|
|
||
| self.assertTrue(found, '{0} does not have {1}'.format(collection, | ||
| element)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| import libscanbuild.analyze as sut | ||
| from . import fixtures |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| import libscanbuild.clang as sut | ||
| from . import fixtures | ||
| import os.path | ||
|
|
||
|
|
||
| class GetClangArgumentsTest(fixtures.TestCase): | ||
| def test_get_clang_arguments(self): | ||
| with fixtures.TempDir() as tmpdir: | ||
| filename = os.path.join(tmpdir, 'test.c') | ||
| with open(filename, 'w') as handle: | ||
| handle.write('') | ||
|
|
||
| result = sut.get_arguments( | ||
| ['clang', '-c', filename, '-DNDEBUG', '-Dvar="this is it"'], | ||
| tmpdir) | ||
|
|
||
| self.assertIn('NDEBUG', result) | ||
| self.assertIn('var="this is it"', result) | ||
|
|
||
| def test_get_clang_arguments_fails(self): | ||
| self.assertRaises( | ||
| Exception, sut.get_arguments, | ||
| ['clang', '-###', '-fsyntax-only', '-x', 'c', 'notexist.c'], '.') | ||
|
|
||
|
|
||
| class GetCheckersTest(fixtures.TestCase): | ||
| def test_get_checkers(self): | ||
| # this test is only to see is not crashing | ||
| result = sut.get_checkers('clang', []) | ||
| self.assertTrue(len(result)) | ||
|
|
||
| def test_get_active_checkers(self): | ||
| # this test is only to see is not crashing | ||
| result = sut.get_active_checkers('clang', []) | ||
| self.assertTrue(len(result)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # The LLVM Compiler Infrastructure | ||
| # | ||
| # This file is distributed under the University of Illinois Open Source | ||
| # License. See LICENSE.TXT for details. | ||
|
|
||
| import libscanbuild.command as sut | ||
| from . import fixtures | ||
| import unittest | ||
|
|
||
|
|
||
| class ParseTest(unittest.TestCase): | ||
|
|
||
| def test_action(self): | ||
| def test(expected, cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| self.assertEqual(expected, opts['action']) | ||
|
|
||
| Link = sut.Action.Link | ||
| test(Link, ['clang', 'source.c']) | ||
|
|
||
| Compile = sut.Action.Compile | ||
| test(Compile, ['clang', '-c', 'source.c']) | ||
| test(Compile, ['clang', '-c', 'source.c', '-MF', 'source.d']) | ||
|
|
||
| Preprocess = sut.Action.Ignored | ||
| test(Preprocess, ['clang', '-E', 'source.c']) | ||
| test(Preprocess, ['clang', '-c', '-E', 'source.c']) | ||
| test(Preprocess, ['clang', '-c', '-M', 'source.c']) | ||
| test(Preprocess, ['clang', '-c', '-MM', 'source.c']) | ||
|
|
||
| def test_optimalizations(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('compile_options', []) | ||
|
|
||
| self.assertEqual(['-O'], test(['clang', '-c', 'source.c', '-O'])) | ||
| self.assertEqual(['-O1'], test(['clang', '-c', 'source.c', '-O1'])) | ||
| self.assertEqual(['-Os'], test(['clang', '-c', 'source.c', '-Os'])) | ||
| self.assertEqual(['-O2'], test(['clang', '-c', 'source.c', '-O2'])) | ||
| self.assertEqual(['-O3'], test(['clang', '-c', 'source.c', '-O3'])) | ||
|
|
||
| def test_language(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('language') | ||
|
|
||
| self.assertEqual(None, test(['clang', '-c', 'source.c'])) | ||
| self.assertEqual('c', test(['clang', '-c', 'source.c', '-x', 'c'])) | ||
| self.assertEqual('cpp', test(['clang', '-c', 'source.c', '-x', 'cpp'])) | ||
|
|
||
| def test_output(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('output') | ||
|
|
||
| self.assertEqual(None, test(['clang', '-c', 'source.c'])) | ||
| self.assertEqual('source.o', | ||
| test(['clang', '-c', '-o', 'source.o', 'source.c'])) | ||
|
|
||
| def test_arch(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('archs_seen', []) | ||
|
|
||
| eq = self.assertEqual | ||
|
|
||
| eq([], test(['clang', '-c', 'source.c'])) | ||
| eq(['mips'], | ||
| test(['clang', '-c', 'source.c', '-arch', 'mips'])) | ||
| eq(['mips', 'i386'], | ||
| test(['clang', '-c', 'source.c', '-arch', 'mips', '-arch', 'i386'])) | ||
|
|
||
| def test_input_file(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('files', []) | ||
|
|
||
| eq = self.assertEqual | ||
|
|
||
| eq(['src.c'], test(['clang', 'src.c'])) | ||
| eq(['src.c'], test(['clang', '-c', 'src.c'])) | ||
| eq(['s1.c', 's2.c'], test(['clang', '-c', 's1.c', 's2.c'])) | ||
|
|
||
| def test_include(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('compile_options', []) | ||
|
|
||
| eq = self.assertEqual | ||
|
|
||
| eq([], test(['clang', '-c', 'src.c'])) | ||
| eq(['-include', '/usr/local/include'], | ||
| test(['clang', '-c', 'src.c', '-include', '/usr/local/include'])) | ||
| eq(['-I.'], | ||
| test(['clang', '-c', 'src.c', '-I.'])) | ||
| eq(['-I', '.'], | ||
| test(['clang', '-c', 'src.c', '-I', '.'])) | ||
| eq(['-I/usr/local/include'], | ||
| test(['clang', '-c', 'src.c', '-I/usr/local/include'])) | ||
| eq(['-I', '/usr/local/include'], | ||
| test(['clang', '-c', 'src.c', '-I', '/usr/local/include'])) | ||
| eq(['-I/opt', '-I', '/opt/otp/include'], | ||
| test(['clang', '-c', 'src.c', '-I/opt', '-I', '/opt/otp/include'])) | ||
| eq(['-isystem', '/path'], | ||
| test(['clang', '-c', 'src.c', '-isystem', '/path'])) | ||
| eq(['-isystem=/path'], | ||
| test(['clang', '-c', 'src.c', '-isystem=/path'])) | ||
|
|
||
| def test_define(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('compile_options', []) | ||
|
|
||
| eq = self.assertEqual | ||
|
|
||
| eq([], test(['clang', '-c', 'src.c'])) | ||
| eq(['-DNDEBUG'], | ||
| test(['clang', '-c', 'src.c', '-DNDEBUG'])) | ||
| eq(['-UNDEBUG'], | ||
| test(['clang', '-c', 'src.c', '-UNDEBUG'])) | ||
| eq(['-Dvar1=val1', '-Dvar2=val2'], | ||
| test(['clang', '-c', 'src.c', '-Dvar1=val1', '-Dvar2=val2'])) | ||
| eq(['-Dvar="val ues"'], | ||
| test(['clang', '-c', 'src.c', '-Dvar="val ues"'])) | ||
|
|
||
| def test_ignored_flags(self): | ||
| def test(flags): | ||
| cmd = ['clang', 'src.o'] | ||
| opts = sut.classify_parameters(cmd + flags) | ||
| self.assertEqual(['src.o'], opts.get('compile_options')) | ||
|
|
||
| test([]) | ||
| test(['-lrt', '-L/opt/company/lib']) | ||
| test(['-static']) | ||
| test(['-Wnoexcept', '-Wall']) | ||
| test(['-mtune=i386', '-mcpu=i386']) | ||
|
|
||
| def test_compile_only_flags(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('compile_options', []) | ||
|
|
||
| eq = self.assertEqual | ||
|
|
||
| eq(['-std=C99'], | ||
| test(['clang', '-c', 'src.c', '-std=C99'])) | ||
| eq(['-nostdinc'], | ||
| test(['clang', '-c', 'src.c', '-nostdinc'])) | ||
| eq(['-isystem', '/image/debian'], | ||
| test(['clang', '-c', 'src.c', '-isystem', '/image/debian'])) | ||
| eq(['-iprefix', '/usr/local'], | ||
| test(['clang', '-c', 'src.c', '-iprefix', '/usr/local'])) | ||
| eq(['-iquote=me'], | ||
| test(['clang', '-c', 'src.c', '-iquote=me'])) | ||
| eq(['-iquote', 'me'], | ||
| test(['clang', '-c', 'src.c', '-iquote', 'me'])) | ||
|
|
||
| def test_compile_and_link_flags(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('compile_options', []) | ||
|
|
||
| eq = self.assertEqual | ||
|
|
||
| eq(['-fsinged-char'], | ||
| test(['clang', '-c', 'src.c', '-fsinged-char'])) | ||
| eq(['-fPIC'], | ||
| test(['clang', '-c', 'src.c', '-fPIC'])) | ||
| eq(['-stdlib=libc++'], | ||
| test(['clang', '-c', 'src.c', '-stdlib=libc++'])) | ||
| eq(['--sysroot', '/'], | ||
| test(['clang', '-c', 'src.c', '--sysroot', '/'])) | ||
| eq(['-isysroot', '/'], | ||
| test(['clang', '-c', 'src.c', '-isysroot', '/'])) | ||
| eq([], | ||
| test(['clang', '-c', 'src.c', '-fsyntax-only'])) | ||
| eq([], | ||
| test(['clang', '-c', 'src.c', '-sectorder', 'a', 'b', 'c'])) | ||
|
|
||
| def test_detect_cxx_from_compiler_name(self): | ||
| def test(cmd): | ||
| opts = sut.classify_parameters(cmd) | ||
| return opts.get('c++') | ||
|
|
||
| eq = self.assertEqual | ||
|
|
||
| eq(False, test(['cc', '-c', 'src.c'])) | ||
| eq(True, test(['c++', '-c', 'src.c'])) | ||
| eq(False, test(['clang', '-c', 'src.c'])) | ||
| eq(True, test(['clang++', '-c', 'src.c'])) | ||
| eq(False, test(['gcc', '-c', 'src.c'])) | ||
| eq(True, test(['g++', '-c', 'src.c'])) |