/clang/tools/scan-build-py/libscanbuild/analyze.py
Python | 780 lines | 776 code | 0 blank | 4 comment | 2 complexity | 144b749dfa8d39744f98478833186aad MD5 | raw file
- # -*- coding: utf-8 -*-
- # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- # See https://llvm.org/LICENSE.txt for license information.
- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- """ This module implements the 'scan-build' command API.
- To run the static analyzer against a build is done in multiple steps:
- -- Intercept: capture the compilation command during the build,
- -- Analyze: run the analyzer against the captured commands,
- -- Report: create a cover report from the analyzer outputs. """
- import re
- import os
- import os.path
- import json
- import logging
- import multiprocessing
- import tempfile
- import functools
- import subprocess
- import contextlib
- import datetime
- import shutil
- import glob
- from collections import defaultdict
- from libscanbuild import command_entry_point, compiler_wrapper, \
- wrapper_environment, run_build, run_command, CtuConfig
- from libscanbuild.arguments import parse_args_for_scan_build, \
- parse_args_for_analyze_build
- from libscanbuild.intercept import capture
- from libscanbuild.report import document
- from libscanbuild.compilation import split_command, classify_source, \
- compiler_language
- from libscanbuild.clang import get_version, get_arguments, get_triple_arch
- from libscanbuild.shell import decode
- __all__ = ['scan_build', 'analyze_build', 'analyze_compiler_wrapper']
- COMPILER_WRAPPER_CC = 'analyze-cc'
- COMPILER_WRAPPER_CXX = 'analyze-c++'
- CTU_EXTDEF_MAP_FILENAME = 'externalDefMap.txt'
- CTU_TEMP_DEFMAP_FOLDER = 'tmpExternalDefMaps'
- @command_entry_point
- def scan_build():
- """ Entry point for scan-build command. """
- args = parse_args_for_scan_build()
- # will re-assign the report directory as new output
- with report_directory(args.output, args.keep_empty) as args.output:
- # Run against a build command. there are cases, when analyzer run
- # is not required. But we need to set up everything for the
- # wrappers, because 'configure' needs to capture the CC/CXX values
- # for the Makefile.
- if args.intercept_first:
- # Run build command with intercept module.
- exit_code = capture(args)
- # Run the analyzer against the captured commands.
- if need_analyzer(args.build):
- govern_analyzer_runs(args)
- else:
- # Run build command and analyzer with compiler wrappers.
- environment = setup_environment(args)
- exit_code = run_build(args.build, env=environment)
- # Cover report generation and bug counting.
- number_of_bugs = document(args)
- # Set exit status as it was requested.
- return number_of_bugs if args.status_bugs else exit_code
- @command_entry_point
- def analyze_build():
- """ Entry point for analyze-build command. """
- args = parse_args_for_analyze_build()
- # will re-assign the report directory as new output
- with report_directory(args.output, args.keep_empty) as args.output:
- # Run the analyzer against a compilation db.
- govern_analyzer_runs(args)
- # Cover report generation and bug counting.
- number_of_bugs = document(args)
- # Set exit status as it was requested.
- return number_of_bugs if args.status_bugs else 0
- def need_analyzer(args):
- """ Check the intent of the build command.
- When static analyzer run against project configure step, it should be
- silent and no need to run the analyzer or generate report.
- To run `scan-build` against the configure step might be necessary,
- when compiler wrappers are used. That's the moment when build setup
- check the compiler and capture the location for the build process. """
- return len(args) and not re.search(r'configure|autogen', args[0])
- def prefix_with(constant, pieces):
- """ From a sequence create another sequence where every second element
- is from the original sequence and the odd elements are the prefix.
- eg.: prefix_with(0, [1,2,3]) creates [0, 1, 0, 2, 0, 3] """
- return [elem for piece in pieces for elem in [constant, piece]]
- def get_ctu_config_from_args(args):
- """ CTU configuration is created from the chosen phases and dir. """
- return (
- CtuConfig(collect=args.ctu_phases.collect,
- analyze=args.ctu_phases.analyze,
- dir=args.ctu_dir,
- extdef_map_cmd=args.extdef_map_cmd)
- if hasattr(args, 'ctu_phases') and hasattr(args.ctu_phases, 'dir')
- else CtuConfig(collect=False, analyze=False, dir='', extdef_map_cmd=''))
- def get_ctu_config_from_json(ctu_conf_json):
- """ CTU configuration is created from the chosen phases and dir. """
- ctu_config = json.loads(ctu_conf_json)
- # Recover namedtuple from json when coming from analyze-cc or analyze-c++
- return CtuConfig(collect=ctu_config[0],
- analyze=ctu_config[1],
- dir=ctu_config[2],
- extdef_map_cmd=ctu_config[3])
- def create_global_ctu_extdef_map(extdef_map_lines):
- """ Takes iterator of individual external definition maps and creates a
- global map keeping only unique names. We leave conflicting names out of
- CTU.
- :param extdef_map_lines: Contains the id of a definition (mangled name) and
- the originating source (the corresponding AST file) name.
- :type extdef_map_lines: Iterator of str.
- :returns: Mangled name - AST file pairs.
- :rtype: List of (str, str) tuples.
- """
- mangled_to_asts = defaultdict(set)
- for line in extdef_map_lines:
- mangled_name, ast_file = line.strip().split(' ', 1)
- mangled_to_asts[mangled_name].add(ast_file)
- mangled_ast_pairs = []
- for mangled_name, ast_files in mangled_to_asts.items():
- if len(ast_files) == 1:
- mangled_ast_pairs.append((mangled_name, next(iter(ast_files))))
- return mangled_ast_pairs
- def merge_ctu_extdef_maps(ctudir):
- """ Merge individual external definition maps into a global one.
- As the collect phase runs parallel on multiple threads, all compilation
- units are separately mapped into a temporary file in CTU_TEMP_DEFMAP_FOLDER.
- These definition maps contain the mangled names and the source
- (AST generated from the source) which had their definition.
- These files should be merged at the end into a global map file:
- CTU_EXTDEF_MAP_FILENAME."""
- def generate_extdef_map_lines(extdefmap_dir):
- """ Iterate over all lines of input files in a determined order. """
- files = glob.glob(os.path.join(extdefmap_dir, '*'))
- files.sort()
- for filename in files:
- with open(filename, 'r') as in_file:
- for line in in_file:
- yield line
- def write_global_map(arch, mangled_ast_pairs):
- """ Write (mangled name, ast file) pairs into final file. """
- extern_defs_map_file = os.path.join(ctudir, arch,
- CTU_EXTDEF_MAP_FILENAME)
- with open(extern_defs_map_file, 'w') as out_file:
- for mangled_name, ast_file in mangled_ast_pairs:
- out_file.write('%s %s\n' % (mangled_name, ast_file))
- triple_arches = glob.glob(os.path.join(ctudir, '*'))
- for triple_path in triple_arches:
- if os.path.isdir(triple_path):
- triple_arch = os.path.basename(triple_path)
- extdefmap_dir = os.path.join(ctudir, triple_arch,
- CTU_TEMP_DEFMAP_FOLDER)
- extdef_map_lines = generate_extdef_map_lines(extdefmap_dir)
- mangled_ast_pairs = create_global_ctu_extdef_map(extdef_map_lines)
- write_global_map(triple_arch, mangled_ast_pairs)
- # Remove all temporary files
- shutil.rmtree(extdefmap_dir, ignore_errors=True)
- def run_analyzer_parallel(args):
- """ Runs the analyzer against the given compilation database. """
- def exclude(filename):
- """ Return true when any excluded directory prefix the filename. """
- return any(re.match(r'^' + directory, filename)
- for directory in args.excludes)
- consts = {
- 'clang': args.clang,
- 'output_dir': args.output,
- 'output_format': args.output_format,
- 'output_failures': args.output_failures,
- 'direct_args': analyzer_params(args),
- 'force_debug': args.force_debug,
- 'ctu': get_ctu_config_from_args(args)
- }
- logging.debug('run analyzer against compilation database')
- with open(args.cdb, 'r') as handle:
- generator = (dict(cmd, **consts)
- for cmd in json.load(handle) if not exclude(cmd['file']))
- # when verbose output requested execute sequentially
- pool = multiprocessing.Pool(1 if args.verbose > 2 else None)
- for current in pool.imap_unordered(run, generator):
- if current is not None:
- # display error message from the static analyzer
- for line in current['error_output']:
- logging.info(line.rstrip())
- pool.close()
- pool.join()
- def govern_analyzer_runs(args):
- """ Governs multiple runs in CTU mode or runs once in normal mode. """
- ctu_config = get_ctu_config_from_args(args)
- # If we do a CTU collect (1st phase) we remove all previous collection
- # data first.
- if ctu_config.collect:
- shutil.rmtree(ctu_config.dir, ignore_errors=True)
- # If the user asked for a collect (1st) and analyze (2nd) phase, we do an
- # all-in-one run where we deliberately remove collection data before and
- # also after the run. If the user asks only for a single phase data is
- # left so multiple analyze runs can use the same data gathered by a single
- # collection run.
- if ctu_config.collect and ctu_config.analyze:
- # CTU strings are coming from args.ctu_dir and extdef_map_cmd,
- # so we can leave it empty
- args.ctu_phases = CtuConfig(collect=True, analyze=False,
- dir='', extdef_map_cmd='')
- run_analyzer_parallel(args)
- merge_ctu_extdef_maps(ctu_config.dir)
- args.ctu_phases = CtuConfig(collect=False, analyze=True,
- dir='', extdef_map_cmd='')
- run_analyzer_parallel(args)
- shutil.rmtree(ctu_config.dir, ignore_errors=True)
- else:
- # Single runs (collect or analyze) are launched from here.
- run_analyzer_parallel(args)
- if ctu_config.collect:
- merge_ctu_extdef_maps(ctu_config.dir)
- def setup_environment(args):
- """ Set up environment for build command to interpose compiler wrapper. """
- environment = dict(os.environ)
- environment.update(wrapper_environment(args))
- environment.update({
- 'CC': COMPILER_WRAPPER_CC,
- 'CXX': COMPILER_WRAPPER_CXX,
- 'ANALYZE_BUILD_CLANG': args.clang if need_analyzer(args.build) else '',
- 'ANALYZE_BUILD_REPORT_DIR': args.output,
- 'ANALYZE_BUILD_REPORT_FORMAT': args.output_format,
- 'ANALYZE_BUILD_REPORT_FAILURES': 'yes' if args.output_failures else '',
- 'ANALYZE_BUILD_PARAMETERS': ' '.join(analyzer_params(args)),
- 'ANALYZE_BUILD_FORCE_DEBUG': 'yes' if args.force_debug else '',
- 'ANALYZE_BUILD_CTU': json.dumps(get_ctu_config_from_args(args))
- })
- return environment
- @command_entry_point
- def analyze_compiler_wrapper():
- """ Entry point for `analyze-cc` and `analyze-c++` compiler wrappers. """
- return compiler_wrapper(analyze_compiler_wrapper_impl)
- def analyze_compiler_wrapper_impl(result, execution):
- """ Implements analyzer compiler wrapper functionality. """
- # don't run analyzer when compilation fails. or when it's not requested.
- if result or not os.getenv('ANALYZE_BUILD_CLANG'):
- return
- # check is it a compilation?
- compilation = split_command(execution.cmd)
- if compilation is None:
- return
- # collect the needed parameters from environment, crash when missing
- parameters = {
- 'clang': os.getenv('ANALYZE_BUILD_CLANG'),
- 'output_dir': os.getenv('ANALYZE_BUILD_REPORT_DIR'),
- 'output_format': os.getenv('ANALYZE_BUILD_REPORT_FORMAT'),
- 'output_failures': os.getenv('ANALYZE_BUILD_REPORT_FAILURES'),
- 'direct_args': os.getenv('ANALYZE_BUILD_PARAMETERS',
- '').split(' '),
- 'force_debug': os.getenv('ANALYZE_BUILD_FORCE_DEBUG'),
- 'directory': execution.cwd,
- 'command': [execution.cmd[0], '-c'] + compilation.flags,
- 'ctu': get_ctu_config_from_json(os.getenv('ANALYZE_BUILD_CTU'))
- }
- # call static analyzer against the compilation
- for source in compilation.files:
- parameters.update({'file': source})
- logging.debug('analyzer parameters %s', parameters)
- current = run(parameters)
- # display error message from the static analyzer
- if current is not None:
- for line in current['error_output']:
- logging.info(line.rstrip())
- @contextlib.contextmanager
- def report_directory(hint, keep):
- """ Responsible for the report directory.
- hint -- could specify the parent directory of the output directory.
- keep -- a boolean value to keep or delete the empty report directory. """
- stamp_format = 'scan-build-%Y-%m-%d-%H-%M-%S-%f-'
- stamp = datetime.datetime.now().strftime(stamp_format)
- parent_dir = os.path.abspath(hint)
- if not os.path.exists(parent_dir):
- os.makedirs(parent_dir)
- name = tempfile.mkdtemp(prefix=stamp, dir=parent_dir)
- logging.info('Report directory created: %s', name)
- try:
- yield name
- finally:
- if os.listdir(name):
- msg = "Run 'scan-view %s' to examine bug reports."
- keep = True
- else:
- if keep:
- msg = "Report directory '%s' contains no report, but kept."
- else:
- msg = "Removing directory '%s' because it contains no report."
- logging.warning(msg, name)
- if not keep:
- os.rmdir(name)
- def analyzer_params(args):
- """ A group of command line arguments can mapped to command
- line arguments of the analyzer. This method generates those. """
- result = []
- if args.store_model:
- result.append('-analyzer-store={0}'.format(args.store_model))
- if args.constraints_model:
- result.append('-analyzer-constraints={0}'.format(
- args.constraints_model))
- if args.internal_stats:
- result.append('-analyzer-stats')
- if args.analyze_headers:
- result.append('-analyzer-opt-analyze-headers')
- if args.stats:
- result.append('-analyzer-checker=debug.Stats')
- if args.maxloop:
- result.extend(['-analyzer-max-loop', str(args.maxloop)])
- if args.output_format:
- result.append('-analyzer-output={0}'.format(args.output_format))
- if args.analyzer_config:
- result.extend(['-analyzer-config', args.analyzer_config])
- if args.verbose >= 4:
- result.append('-analyzer-display-progress')
- if args.plugins:
- result.extend(prefix_with('-load', args.plugins))
- if args.enable_checker:
- checkers = ','.join(args.enable_checker)
- result.extend(['-analyzer-checker', checkers])
- if args.disable_checker:
- checkers = ','.join(args.disable_checker)
- result.extend(['-analyzer-disable-checker', checkers])
- return prefix_with('-Xclang', result)
- 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', # entry from compilation database
- 'directory', # entry from compilation database
- 'file', # entry from compilation database
- 'clang', # clang executable name (and path)
- 'direct_args', # arguments from command line
- 'force_debug', # kill non debug macros
- 'output_dir', # where generated report files shall go
- 'output_format', # it's 'plist', 'html', both or plist-multi-file
- 'output_failures', # generate crash reports or not
- 'ctu']) # ctu control options
- 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 possible 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')
- command = command if isinstance(command, list) else decode(command)
- logging.debug("Run analyzer against '%s'", command)
- opts.update(classify_parameters(command))
- return arch_check(opts)
- except Exception:
- logging.error("Problem occurred during analyzis.", exc_info=1)
- return None
- @require(['clang', 'directory', 'flags', 'file', 'output_dir', 'language',
- '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():
- """ Generate preprocessor file extension. """
- mapping = {'objective-c++': '.mii', 'objective-c': '.mi', 'c++': '.ii'}
- return mapping.get(opts['language'], '.i')
- def destination():
- """ Creates failures directory if not exits yet. """
- failures_dir = os.path.join(opts['output_dir'], 'failures')
- if not os.path.isdir(failures_dir):
- os.makedirs(failures_dir)
- return failures_dir
- # Classify error type: when Clang terminated by a signal it's a 'Crash'.
- # (python subprocess Popen.returncode is negative when child terminated
- # by signal.) Everything else is 'Other Error'.
- error = 'crash' if opts['exit_code'] < 0 else 'other_error'
- # Create preprocessor output file name. (This is blindly following the
- # Perl implementation.)
- (handle, name) = tempfile.mkstemp(suffix=extension(),
- prefix='clang_' + error + '_',
- dir=destination())
- os.close(handle)
- # Execute Clang again, but run the syntax check only.
- cwd = opts['directory']
- cmd = get_arguments(
- [opts['clang'], '-fsyntax-only', '-E'
- ] + opts['flags'] + [opts['file'], '-o', name], cwd)
- run_command(cmd, cwd=cwd)
- # write general information about the crash
- 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(opts['clang']))
- handle.close()
- # write the captured output too
- with open(name + '.stderr.txt', 'w') as handle:
- handle.writelines(opts['error_output'])
- handle.close()
- @require(['clang', 'directory', 'flags', 'direct_args', 'file', 'output_dir',
- 'output_format'])
- 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. """
- def target():
- """ Creates output file name for reports. """
- if opts['output_format'] in {
- 'plist',
- 'plist-html',
- 'plist-multi-file'}:
- (handle, name) = tempfile.mkstemp(prefix='report-',
- suffix='.plist',
- dir=opts['output_dir'])
- os.close(handle)
- return name
- return opts['output_dir']
- try:
- cwd = opts['directory']
- cmd = get_arguments([opts['clang'], '--analyze'] +
- opts['direct_args'] + opts['flags'] +
- [opts['file'], '-o', target()],
- cwd)
- output = run_command(cmd, cwd=cwd)
- return {'error_output': output, 'exit_code': 0}
- except subprocess.CalledProcessError as ex:
- result = {'error_output': ex.output, 'exit_code': ex.returncode}
- if opts.get('output_failures', False):
- opts.update(result)
- continuation(opts)
- return result
- def extdef_map_list_src_to_ast(extdef_src_list):
- """ Turns textual external definition map list with source files into an
- external definition map list with ast files. """
- extdef_ast_list = []
- for extdef_src_txt in extdef_src_list:
- mangled_name, path = extdef_src_txt.split(" ", 1)
- # Normalize path on windows as well
- path = os.path.splitdrive(path)[1]
- # Make relative path out of absolute
- path = path[1:] if path[0] == os.sep else path
- ast_path = os.path.join("ast", path + ".ast")
- extdef_ast_list.append(mangled_name + " " + ast_path)
- return extdef_ast_list
- @require(['clang', 'directory', 'flags', 'direct_args', 'file', 'ctu'])
- def ctu_collect_phase(opts):
- """ Preprocess source by generating all data needed by CTU analysis. """
- def generate_ast(triple_arch):
- """ Generates ASTs for the current compilation command. """
- args = opts['direct_args'] + opts['flags']
- ast_joined_path = os.path.join(opts['ctu'].dir, triple_arch, 'ast',
- os.path.realpath(opts['file'])[1:] +
- '.ast')
- ast_path = os.path.abspath(ast_joined_path)
- ast_dir = os.path.dirname(ast_path)
- if not os.path.isdir(ast_dir):
- try:
- os.makedirs(ast_dir)
- except OSError:
- # In case an other process already created it.
- pass
- ast_command = [opts['clang'], '-emit-ast']
- ast_command.extend(args)
- ast_command.append('-w')
- ast_command.append(opts['file'])
- ast_command.append('-o')
- ast_command.append(ast_path)
- logging.debug("Generating AST using '%s'", ast_command)
- run_command(ast_command, cwd=opts['directory'])
- def map_extdefs(triple_arch):
- """ Generate external definition map file for the current source. """
- args = opts['direct_args'] + opts['flags']
- extdefmap_command = [opts['ctu'].extdef_map_cmd]
- extdefmap_command.append(opts['file'])
- extdefmap_command.append('--')
- extdefmap_command.extend(args)
- logging.debug("Generating external definition map using '%s'",
- extdefmap_command)
- extdef_src_list = run_command(extdefmap_command, cwd=opts['directory'])
- extdef_ast_list = extdef_map_list_src_to_ast(extdef_src_list)
- extern_defs_map_folder = os.path.join(opts['ctu'].dir, triple_arch,
- CTU_TEMP_DEFMAP_FOLDER)
- if not os.path.isdir(extern_defs_map_folder):
- try:
- os.makedirs(extern_defs_map_folder)
- except OSError:
- # In case an other process already created it.
- pass
- if extdef_ast_list:
- with tempfile.NamedTemporaryFile(mode='w',
- dir=extern_defs_map_folder,
- delete=False) as out_file:
- out_file.write("\n".join(extdef_ast_list) + "\n")
- cwd = opts['directory']
- cmd = [opts['clang'], '--analyze'] + opts['direct_args'] + opts['flags'] \
- + [opts['file']]
- triple_arch = get_triple_arch(cmd, cwd)
- generate_ast(triple_arch)
- map_extdefs(triple_arch)
- @require(['ctu'])
- def dispatch_ctu(opts, continuation=run_analyzer):
- """ Execute only one phase of 2 phases of CTU if needed. """
- ctu_config = opts['ctu']
- if ctu_config.collect or ctu_config.analyze:
- assert ctu_config.collect != ctu_config.analyze
- if ctu_config.collect:
- return ctu_collect_phase(opts)
- if ctu_config.analyze:
- cwd = opts['directory']
- cmd = [opts['clang'], '--analyze'] + opts['direct_args'] \
- + opts['flags'] + [opts['file']]
- triarch = get_triple_arch(cmd, cwd)
- ctu_options = ['ctu-dir=' + os.path.join(ctu_config.dir, triarch),
- 'experimental-enable-naive-ctu-analysis=true']
- analyzer_options = prefix_with('-analyzer-config', ctu_options)
- direct_options = prefix_with('-Xanalyzer', analyzer_options)
- opts['direct_args'].extend(direct_options)
- return continuation(opts)
- @require(['flags', 'force_debug'])
- def filter_debug_flags(opts, continuation=dispatch_ctu):
- """ Filter out nondebug macros when requested. """
- if opts.pop('force_debug'):
- # lazy implementation just append an undefine macro at the end
- opts.update({'flags': opts['flags'] + ['-UNDEBUG']})
- return continuation(opts)
- @require(['language', 'compiler', 'file', 'flags'])
- def language_check(opts, continuation=filter_debug_flags):
- """ Find out the language from command line parameters or file name
- extension. The decision also influenced by the compiler invocation. """
- accepted = frozenset({
- 'c', 'c++', 'objective-c', 'objective-c++', 'c-cpp-output',
- 'c++-cpp-output', 'objective-c-cpp-output'
- })
- # language can be given as a parameter...
- language = opts.pop('language')
- compiler = opts.pop('compiler')
- # ... or find out from source file extension
- if language is None and compiler is not None:
- language = classify_source(opts['file'], compiler == 'c')
- if language is None:
- logging.debug('skip analysis, language not known')
- return None
- elif language not in accepted:
- logging.debug('skip analysis, language not supported')
- return None
- else:
- logging.debug('analysis, language: %s', language)
- opts.update({'language': language,
- 'flags': ['-x', language] + opts['flags']})
- return continuation(opts)
- @require(['arch_list', 'flags'])
- def arch_check(opts, continuation=language_check):
- """ Do run analyzer through one of the given architectures. """
- disabled = frozenset({'ppc', 'ppc64'})
- received_list = opts.pop('arch_list')
- if received_list:
- # filter out disabled architectures and -arch switches
- filtered_list = [a for a in received_list if a not in disabled]
- if filtered_list:
- # 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.
- current = filtered_list.pop()
- logging.debug('analysis, on arch: %s', current)
- opts.update({'flags': ['-arch', current] + opts['flags']})
- return continuation(opts)
- else:
- logging.debug('skip analysis, found not supported arch')
- return None
- else:
- logging.debug('analysis, on default arch')
- return continuation(opts)
- # To have good results from static analyzer certain compiler options shall be
- # omitted. The compiler flag filtering only affects the static analyzer run.
- #
- # Keys are the option name, value number of options to skip
- IGNORED_FLAGS = {
- '-c': 0, # compile option will be overwritten
- '-fsyntax-only': 0, # static analyzer option will be overwritten
- '-o': 1, # will set up own output file
- # flags below are inherited from the perl implementation.
- '-g': 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
- }
- def classify_parameters(command):
- """ Prepare compiler flags (filters some and add others) and take out
- language (-x) and architecture (-arch) flags for future processing. """
- result = {
- 'flags': [], # the filtered compiler flags
- 'arch_list': [], # list of architecture flags
- 'language': None, # compilation language, None, if not specified
- 'compiler': compiler_language(command) # 'c' or 'c++'
- }
- # iterate on the compile options
- args = iter(command[1:])
- for arg in args:
- # take arch flags into a separate basket
- if arg == '-arch':
- result['arch_list'].append(next(args))
- # take language
- elif arg == '-x':
- result['language'] = next(args)
- # parameters which looks source file are not flags
- elif re.match(r'^[^-].+', arg) and classify_source(arg):
- pass
- # ignore some flags
- elif arg in IGNORED_FLAGS:
- count = IGNORED_FLAGS[arg]
- for _ in range(count):
- next(args)
- # we don't care about extra warnings, but we should suppress ones
- # that we don't want to see.
- elif re.match(r'^-W.+', arg) and not re.match(r'^-Wno-.+', arg):
- pass
- # and consider everything else as compilation flag.
- else:
- result['flags'].append(arg)
- return result