brintos

brintos / llvm-project-archived public Read only

0
0
Text · 31.3 KiB · 72aac8f Raw
887 lines · python
1# -*- coding: utf-8 -*-2# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3# See https://llvm.org/LICENSE.txt for license information.4# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5""" This module implements the 'scan-build' command API.6 7To run the static analyzer against a build is done in multiple steps:8 9 -- Intercept: capture the compilation command during the build,10 -- Analyze:   run the analyzer against the captured commands,11 -- Report:    create a cover report from the analyzer outputs.  """12 13import re14import os15import os.path16import json17import logging18import multiprocessing19import tempfile20import functools21import subprocess22import contextlib23import datetime24import shutil25import glob26from collections import defaultdict27 28from libscanbuild import (29    command_entry_point,30    compiler_wrapper,31    wrapper_environment,32    run_build,33    run_command,34    CtuConfig,35)36from libscanbuild.arguments import (37    parse_args_for_scan_build,38    parse_args_for_analyze_build,39)40from libscanbuild.intercept import capture41from libscanbuild.report import document42from libscanbuild.compilation import split_command, classify_source, compiler_language43from libscanbuild.clang import (44    get_version,45    get_arguments,46    get_triple_arch,47    ClangErrorException,48)49from libscanbuild.shell import decode50 51__all__ = ["scan_build", "analyze_build", "analyze_compiler_wrapper"]52 53scanbuild_dir = os.path.dirname(os.path.realpath(__import__("sys").argv[0]))54 55COMPILER_WRAPPER_CC = os.path.join(scanbuild_dir, "..", "libexec", "analyze-cc")56COMPILER_WRAPPER_CXX = os.path.join(scanbuild_dir, "..", "libexec", "analyze-c++")57 58CTU_EXTDEF_MAP_FILENAME = "externalDefMap.txt"59CTU_TEMP_DEFMAP_FOLDER = "tmpExternalDefMaps"60 61 62@command_entry_point63def scan_build():64    """Entry point for scan-build command."""65 66    args = parse_args_for_scan_build()67    # will re-assign the report directory as new output68    with report_directory(69        args.output, args.keep_empty, args.output_format70    ) as args.output:71        # Run against a build command. there are cases, when analyzer run72        # is not required. But we need to set up everything for the73        # wrappers, because 'configure' needs to capture the CC/CXX values74        # for the Makefile.75        if args.intercept_first:76            # Run build command with intercept module.77            exit_code = capture(args)78            # Run the analyzer against the captured commands.79            if need_analyzer(args.build):80                govern_analyzer_runs(args)81        else:82            # Run build command and analyzer with compiler wrappers.83            environment = setup_environment(args)84            exit_code = run_build(args.build, env=environment)85        # Cover report generation and bug counting.86        number_of_bugs = document(args)87        # Set exit status as it was requested.88        return number_of_bugs if args.status_bugs else exit_code89 90 91@command_entry_point92def analyze_build():93    """Entry point for analyze-build command."""94 95    args = parse_args_for_analyze_build()96    # will re-assign the report directory as new output97    with report_directory(98        args.output, args.keep_empty, args.output_format99    ) as args.output:100        # Run the analyzer against a compilation db.101        govern_analyzer_runs(args)102        # Cover report generation and bug counting.103        number_of_bugs = document(args)104        # Set exit status as it was requested.105        return number_of_bugs if args.status_bugs else 0106 107 108def need_analyzer(args):109    """Check the intent of the build command.110 111    When static analyzer run against project configure step, it should be112    silent and no need to run the analyzer or generate report.113 114    To run `scan-build` against the configure step might be necessary,115    when compiler wrappers are used. That's the moment when build setup116    check the compiler and capture the location for the build process."""117 118    return len(args) and not re.search(r"configure|autogen", args[0])119 120 121def prefix_with(constant, pieces):122    """From a sequence create another sequence where every second element123    is from the original sequence and the odd elements are the prefix.124 125    eg.: prefix_with(0, [1,2,3]) creates [0, 1, 0, 2, 0, 3]"""126 127    return [elem for piece in pieces for elem in [constant, piece]]128 129 130def get_ctu_config_from_args(args):131    """CTU configuration is created from the chosen phases and dir."""132 133    return (134        CtuConfig(135            collect=args.ctu_phases.collect,136            analyze=args.ctu_phases.analyze,137            dir=args.ctu_dir,138            extdef_map_cmd=args.extdef_map_cmd,139        )140        if hasattr(args, "ctu_phases") and hasattr(args.ctu_phases, "dir")141        else CtuConfig(collect=False, analyze=False, dir="", extdef_map_cmd="")142    )143 144 145def get_ctu_config_from_json(ctu_conf_json):146    """CTU configuration is created from the chosen phases and dir."""147 148    ctu_config = json.loads(ctu_conf_json)149    # Recover namedtuple from json when coming from analyze-cc or analyze-c++150    return CtuConfig(151        collect=ctu_config[0],152        analyze=ctu_config[1],153        dir=ctu_config[2],154        extdef_map_cmd=ctu_config[3],155    )156 157 158def create_global_ctu_extdef_map(extdef_map_lines):159    """Takes iterator of individual external definition maps and creates a160    global map keeping only unique names. We leave conflicting names out of161    CTU.162 163    :param extdef_map_lines: Contains the id of a definition (mangled name) and164    the originating source (the corresponding AST file) name.165    :type extdef_map_lines: Iterator of str.166    :returns: Mangled name - AST file pairs.167    :rtype: List of (str, str) tuples.168    """169 170    mangled_to_asts = defaultdict(set)171 172    for line in extdef_map_lines:173        mangled_name, ast_file = line.strip().split(" ", 1)174        mangled_to_asts[mangled_name].add(ast_file)175 176    mangled_ast_pairs = []177 178    for mangled_name, ast_files in mangled_to_asts.items():179        if len(ast_files) == 1:180            mangled_ast_pairs.append((mangled_name, next(iter(ast_files))))181 182    return mangled_ast_pairs183 184 185def merge_ctu_extdef_maps(ctudir):186    """Merge individual external definition maps into a global one.187 188    As the collect phase runs parallel on multiple threads, all compilation189    units are separately mapped into a temporary file in CTU_TEMP_DEFMAP_FOLDER.190    These definition maps contain the mangled names and the source191    (AST generated from the source) which had their definition.192    These files should be merged at the end into a global map file:193    CTU_EXTDEF_MAP_FILENAME."""194 195    def generate_extdef_map_lines(extdefmap_dir):196        """Iterate over all lines of input files in a determined order."""197 198        files = glob.glob(os.path.join(extdefmap_dir, "*"))199        files.sort()200        for filename in files:201            with open(filename, "r") as in_file:202                for line in in_file:203                    yield line204 205    def write_global_map(arch, mangled_ast_pairs):206        """Write (mangled name, ast file) pairs into final file."""207 208        extern_defs_map_file = os.path.join(ctudir, arch, CTU_EXTDEF_MAP_FILENAME)209        with open(extern_defs_map_file, "w") as out_file:210            for mangled_name, ast_file in mangled_ast_pairs:211                out_file.write("%s %s\n" % (mangled_name, ast_file))212 213    triple_arches = glob.glob(os.path.join(ctudir, "*"))214    for triple_path in triple_arches:215        if os.path.isdir(triple_path):216            triple_arch = os.path.basename(triple_path)217            extdefmap_dir = os.path.join(ctudir, triple_arch, CTU_TEMP_DEFMAP_FOLDER)218 219            extdef_map_lines = generate_extdef_map_lines(extdefmap_dir)220            mangled_ast_pairs = create_global_ctu_extdef_map(extdef_map_lines)221            write_global_map(triple_arch, mangled_ast_pairs)222 223            # Remove all temporary files224            shutil.rmtree(extdefmap_dir, ignore_errors=True)225 226 227def run_analyzer_parallel(args):228    """Runs the analyzer against the given compilation database."""229 230    def exclude(filename, directory):231        """Return true when any excluded directory prefix the filename."""232        if not os.path.isabs(filename):233            # filename is either absolute or relative to directory. Need to turn234            # it to absolute since 'args.excludes' are absolute paths.235            filename = os.path.normpath(os.path.join(directory, filename))236        return any(237            re.match(r"^" + exclude_directory, filename)238            for exclude_directory in args.excludes239        )240 241    consts = {242        "clang": args.clang,243        "output_dir": args.output,244        "output_format": args.output_format,245        "output_failures": args.output_failures,246        "direct_args": analyzer_params(args),247        "force_debug": args.force_debug,248        "ctu": get_ctu_config_from_args(args),249    }250 251    logging.debug("run analyzer against compilation database")252    with open(args.cdb, "r") as handle:253        generator = (254            dict(cmd, **consts)255            for cmd in json.load(handle)256            if not exclude(cmd["file"], cmd["directory"])257        )258        # when verbose output requested execute sequentially259        pool = multiprocessing.Pool(1 if args.verbose > 2 else None)260        for current in pool.imap_unordered(run, generator):261            if current is not None:262                # display error message from the static analyzer263                for line in current["error_output"]:264                    logging.info(line.rstrip())265        pool.close()266        pool.join()267 268 269def govern_analyzer_runs(args):270    """Governs multiple runs in CTU mode or runs once in normal mode."""271 272    ctu_config = get_ctu_config_from_args(args)273    # If we do a CTU collect (1st phase) we remove all previous collection274    # data first.275    if ctu_config.collect:276        shutil.rmtree(ctu_config.dir, ignore_errors=True)277 278    # If the user asked for a collect (1st) and analyze (2nd) phase, we do an279    # all-in-one run where we deliberately remove collection data before and280    # also after the run. If the user asks only for a single phase data is281    # left so multiple analyze runs can use the same data gathered by a single282    # collection run.283    if ctu_config.collect and ctu_config.analyze:284        # CTU strings are coming from args.ctu_dir and extdef_map_cmd,285        # so we can leave it empty286        args.ctu_phases = CtuConfig(287            collect=True, analyze=False, dir="", extdef_map_cmd=""288        )289        run_analyzer_parallel(args)290        merge_ctu_extdef_maps(ctu_config.dir)291        args.ctu_phases = CtuConfig(292            collect=False, analyze=True, dir="", extdef_map_cmd=""293        )294        run_analyzer_parallel(args)295        shutil.rmtree(ctu_config.dir, ignore_errors=True)296    else:297        # Single runs (collect or analyze) are launched from here.298        run_analyzer_parallel(args)299        if ctu_config.collect:300            merge_ctu_extdef_maps(ctu_config.dir)301 302 303def setup_environment(args):304    """Set up environment for build command to interpose compiler wrapper."""305 306    environment = dict(os.environ)307    environment.update(wrapper_environment(args))308    environment.update(309        {310            "CC": COMPILER_WRAPPER_CC,311            "CXX": COMPILER_WRAPPER_CXX,312            "ANALYZE_BUILD_CLANG": args.clang if need_analyzer(args.build) else "",313            "ANALYZE_BUILD_REPORT_DIR": args.output,314            "ANALYZE_BUILD_REPORT_FORMAT": args.output_format,315            "ANALYZE_BUILD_REPORT_FAILURES": "yes" if args.output_failures else "",316            "ANALYZE_BUILD_PARAMETERS": " ".join(analyzer_params(args)),317            "ANALYZE_BUILD_FORCE_DEBUG": "yes" if args.force_debug else "",318            "ANALYZE_BUILD_CTU": json.dumps(get_ctu_config_from_args(args)),319        }320    )321    return environment322 323 324@command_entry_point325def analyze_compiler_wrapper():326    """Entry point for `analyze-cc` and `analyze-c++` compiler wrappers."""327 328    return compiler_wrapper(analyze_compiler_wrapper_impl)329 330 331def analyze_compiler_wrapper_impl(result, execution):332    """Implements analyzer compiler wrapper functionality."""333 334    # don't run analyzer when compilation fails. or when it's not requested.335    if result or not os.getenv("ANALYZE_BUILD_CLANG"):336        return337 338    # check is it a compilation?339    compilation = split_command(execution.cmd)340    if compilation is None:341        return342    # collect the needed parameters from environment, crash when missing343    parameters = {344        "clang": os.getenv("ANALYZE_BUILD_CLANG"),345        "output_dir": os.getenv("ANALYZE_BUILD_REPORT_DIR"),346        "output_format": os.getenv("ANALYZE_BUILD_REPORT_FORMAT"),347        "output_failures": os.getenv("ANALYZE_BUILD_REPORT_FAILURES"),348        "direct_args": os.getenv("ANALYZE_BUILD_PARAMETERS", "").split(" "),349        "force_debug": os.getenv("ANALYZE_BUILD_FORCE_DEBUG"),350        "directory": execution.cwd,351        "command": [execution.cmd[0], "-c"] + compilation.flags,352        "ctu": get_ctu_config_from_json(os.getenv("ANALYZE_BUILD_CTU")),353    }354    # call static analyzer against the compilation355    for source in compilation.files:356        parameters.update({"file": source})357        logging.debug("analyzer parameters %s", parameters)358        current = run(parameters)359        # display error message from the static analyzer360        if current is not None:361            for line in current["error_output"]:362                logging.info(line.rstrip())363 364 365@contextlib.contextmanager366def report_directory(hint, keep, output_format):367    """Responsible for the report directory.368 369    hint -- could specify the parent directory of the output directory.370    keep -- a boolean value to keep or delete the empty report directory."""371 372    stamp_format = "scan-build-%Y-%m-%d-%H-%M-%S-%f-"373    stamp = datetime.datetime.now().strftime(stamp_format)374    parent_dir = os.path.abspath(hint)375    if not os.path.exists(parent_dir):376        os.makedirs(parent_dir)377    name = tempfile.mkdtemp(prefix=stamp, dir=parent_dir)378 379    logging.info("Report directory created: %s", name)380 381    try:382        yield name383    finally:384        args = (name,)385        if os.listdir(name):386            if output_format not in ["sarif", "sarif-html"]:  # FIXME:387                # 'scan-view' currently does not support sarif format.388                msg = "Run 'scan-view %s' to examine bug reports."389            elif output_format == "sarif-html":390                msg = (391                    "Run 'scan-view %s' to examine bug reports or see "392                    "merged sarif results at %s/results-merged.sarif."393                )394                args = (name, name)395            else:396                msg = "View merged sarif results at %s/results-merged.sarif."397            keep = True398        else:399            if keep:400                msg = "Report directory '%s' contains no report, but kept."401            else:402                msg = "Removing directory '%s' because it contains no report."403        logging.warning(msg, *args)404 405        if not keep:406            os.rmdir(name)407 408 409def analyzer_params(args):410    """A group of command line arguments can mapped to command411    line arguments of the analyzer. This method generates those."""412 413    result = []414 415    if args.constraints_model:416        result.append("-analyzer-constraints={0}".format(args.constraints_model))417    if args.internal_stats:418        result.append("-analyzer-stats")419    if args.analyze_headers:420        result.append("-analyzer-opt-analyze-headers")421    if args.stats:422        result.append("-analyzer-checker=debug.Stats")423    if args.maxloop:424        result.extend(["-analyzer-max-loop", str(args.maxloop)])425    if args.output_format:426        result.append("-analyzer-output={0}".format(args.output_format))427    if args.analyzer_config:428        result.extend(["-analyzer-config", args.analyzer_config])429    if args.verbose >= 4:430        result.append("-analyzer-display-progress")431    if args.plugins:432        result.extend(prefix_with("-load", args.plugins))433    if args.enable_checker:434        checkers = ",".join(args.enable_checker)435        result.extend(["-analyzer-checker", checkers])436    if args.disable_checker:437        checkers = ",".join(args.disable_checker)438        result.extend(["-analyzer-disable-checker", checkers])439 440    return prefix_with("-Xclang", result)441 442 443def require(required):444    """Decorator for checking the required values in state.445 446    It checks the required attributes in the passed state and stop when447    any of those is missing."""448 449    def decorator(function):450        @functools.wraps(function)451        def wrapper(*args, **kwargs):452            for key in required:453                if key not in args[0]:454                    raise KeyError(455                        "{0} not passed to {1}".format(key, function.__name__)456                    )457 458            return function(*args, **kwargs)459 460        return wrapper461 462    return decorator463 464 465@require(466    [467        "command",  # entry from compilation database468        "directory",  # entry from compilation database469        "file",  # entry from compilation database470        "clang",  # clang executable name (and path)471        "direct_args",  # arguments from command line472        "force_debug",  # kill non debug macros473        "output_dir",  # where generated report files shall go474        "output_format",  # it's 'plist', 'html', 'plist-html', 'plist-multi-file', 'sarif', or 'sarif-html'475        "output_failures",  # generate crash reports or not476        "ctu",477    ]478)  # ctu control options479def run(opts):480    """Entry point to run (or not) static analyzer against a single entry481    of the compilation database.482 483    This complex task is decomposed into smaller methods which are calling484    each other in chain. If the analysis is not possible the given method485    just return and break the chain.486 487    The passed parameter is a python dictionary. Each method first check488    that the needed parameters received. (This is done by the 'require'489    decorator. It's like an 'assert' to check the contract between the490    caller and the called method.)"""491 492    try:493        command = opts.pop("command")494        command = command if isinstance(command, list) else decode(command)495        logging.debug("Run analyzer against '%s'", command)496        opts.update(classify_parameters(command))497 498        return arch_check(opts)499    except Exception:500        logging.error("Problem occurred during analysis.", exc_info=1)501        return None502 503 504@require(505    [506        "clang",507        "directory",508        "flags",509        "file",510        "output_dir",511        "language",512        "error_output",513        "exit_code",514    ]515)516def report_failure(opts):517    """Create report when analyzer failed.518 519    The major report is the preprocessor output. The output filename generated520    randomly. The compiler output also captured into '.stderr.txt' file.521    And some more execution context also saved into '.info.txt' file."""522 523    def extension():524        """Generate preprocessor file extension."""525 526        mapping = {"objective-c++": ".mii", "objective-c": ".mi", "c++": ".ii"}527        return mapping.get(opts["language"], ".i")528 529    def destination():530        """Creates failures directory if not exits yet."""531 532        failures_dir = os.path.join(opts["output_dir"], "failures")533        if not os.path.isdir(failures_dir):534            os.makedirs(failures_dir)535        return failures_dir536 537    # Classify error type: when Clang terminated by a signal it's a 'Crash'.538    # (python subprocess Popen.returncode is negative when child terminated539    # by signal.) Everything else is 'Other Error'.540    error = "crash" if opts["exit_code"] < 0 else "other_error"541    # Create preprocessor output file name. (This is blindly following the542    # Perl implementation.)543    (handle, name) = tempfile.mkstemp(544        suffix=extension(), prefix="clang_" + error + "_", dir=destination()545    )546    os.close(handle)547    # Execute Clang again, but run the syntax check only.548    cwd = opts["directory"]549    cmd = (550        [opts["clang"], "-fsyntax-only", "-E"]551        + opts["flags"]552        + [opts["file"], "-o", name]553    )554    try:555        cmd = get_arguments(cmd, cwd)556        run_command(cmd, cwd=cwd)557    except subprocess.CalledProcessError:558        pass559    except ClangErrorException:560        pass561    # write general information about the crash562    with open(name + ".info.txt", "w") as handle:563        handle.write(opts["file"] + os.linesep)564        handle.write(error.title().replace("_", " ") + os.linesep)565        handle.write(" ".join(cmd) + os.linesep)566        handle.write(" ".join(os.uname()) + os.linesep)567        handle.write(get_version(opts["clang"]))568        handle.close()569    # write the captured output too570    with open(name + ".stderr.txt", "w") as handle:571        handle.writelines(opts["error_output"])572        handle.close()573 574 575@require(576    [577        "clang",578        "directory",579        "flags",580        "direct_args",581        "file",582        "output_dir",583        "output_format",584    ]585)586def run_analyzer(opts, continuation=report_failure):587    """It assembles the analysis command line and executes it. Capture the588    output of the analysis and returns with it. If failure reports are589    requested, it calls the continuation to generate it."""590 591    def target():592        """Creates output file name for reports."""593        if opts["output_format"] in {"plist", "plist-html", "plist-multi-file"}:594            (handle, name) = tempfile.mkstemp(595                prefix="report-", suffix=".plist", dir=opts["output_dir"]596            )597            os.close(handle)598            return name599        elif opts["output_format"] in {"sarif", "sarif-html"}:600            (handle, name) = tempfile.mkstemp(601                prefix="result-", suffix=".sarif", dir=opts["output_dir"]602            )603            os.close(handle)604            return name605        return opts["output_dir"]606 607    try:608        cwd = opts["directory"]609        cmd = get_arguments(610            [opts["clang"], "--analyze"]611            + opts["direct_args"]612            + opts["flags"]613            + [opts["file"], "-o", target()],614            cwd,615        )616        output = run_command(cmd, cwd=cwd)617        return {"error_output": output, "exit_code": 0}618    except subprocess.CalledProcessError as ex:619        result = {"error_output": ex.output, "exit_code": ex.returncode}620        if opts.get("output_failures", False):621            opts.update(result)622            continuation(opts)623        return result624    except ClangErrorException as ex:625        result = {"error_output": ex.error, "exit_code": 0}626        if opts.get("output_failures", False):627            opts.update(result)628            continuation(opts)629        return result630 631 632def extdef_map_list_src_to_ast(extdef_src_list):633    """Turns textual external definition map list with source files into an634    external definition map list with ast files."""635 636    extdef_ast_list = []637    for extdef_src_txt in extdef_src_list:638        mangled_name, path = extdef_src_txt.split(" ", 1)639        # Normalize path on windows as well640        path = os.path.splitdrive(path)[1]641        # Make relative path out of absolute642        path = path[1:] if path[0] == os.sep else path643        ast_path = os.path.join("ast", path + ".ast")644        extdef_ast_list.append(mangled_name + " " + ast_path)645    return extdef_ast_list646 647 648@require(["clang", "directory", "flags", "direct_args", "file", "ctu"])649def ctu_collect_phase(opts):650    """Preprocess source by generating all data needed by CTU analysis."""651 652    def generate_ast(triple_arch):653        """Generates ASTs for the current compilation command."""654 655        args = opts["direct_args"] + opts["flags"]656        ast_joined_path = os.path.join(657            opts["ctu"].dir,658            triple_arch,659            "ast",660            os.path.realpath(opts["file"])[1:] + ".ast",661        )662        ast_path = os.path.abspath(ast_joined_path)663        ast_dir = os.path.dirname(ast_path)664        if not os.path.isdir(ast_dir):665            try:666                os.makedirs(ast_dir)667            except OSError:668                # In case an other process already created it.669                pass670        ast_command = [opts["clang"], "-emit-ast"]671        ast_command.extend(args)672        ast_command.append("-w")673        ast_command.append(opts["file"])674        ast_command.append("-o")675        ast_command.append(ast_path)676        logging.debug("Generating AST using '%s'", ast_command)677        run_command(ast_command, cwd=opts["directory"])678 679    def map_extdefs(triple_arch):680        """Generate external definition map file for the current source."""681 682        args = opts["direct_args"] + opts["flags"]683        extdefmap_command = [opts["ctu"].extdef_map_cmd]684        extdefmap_command.append(opts["file"])685        extdefmap_command.append("--")686        extdefmap_command.extend(args)687        logging.debug(688            "Generating external definition map using '%s'", extdefmap_command689        )690        extdef_src_list = run_command(extdefmap_command, cwd=opts["directory"])691        extdef_ast_list = extdef_map_list_src_to_ast(extdef_src_list)692        extern_defs_map_folder = os.path.join(693            opts["ctu"].dir, triple_arch, CTU_TEMP_DEFMAP_FOLDER694        )695        if not os.path.isdir(extern_defs_map_folder):696            try:697                os.makedirs(extern_defs_map_folder)698            except OSError:699                # In case an other process already created it.700                pass701        if extdef_ast_list:702            with tempfile.NamedTemporaryFile(703                mode="w", dir=extern_defs_map_folder, delete=False704            ) as out_file:705                out_file.write("\n".join(extdef_ast_list) + "\n")706 707    cwd = opts["directory"]708    cmd = (709        [opts["clang"], "--analyze"]710        + opts["direct_args"]711        + opts["flags"]712        + [opts["file"]]713    )714    triple_arch = get_triple_arch(cmd, cwd)715    generate_ast(triple_arch)716    map_extdefs(triple_arch)717 718 719@require(["ctu"])720def dispatch_ctu(opts, continuation=run_analyzer):721    """Execute only one phase of 2 phases of CTU if needed."""722 723    ctu_config = opts["ctu"]724 725    if ctu_config.collect or ctu_config.analyze:726        assert ctu_config.collect != ctu_config.analyze727        if ctu_config.collect:728            return ctu_collect_phase(opts)729        if ctu_config.analyze:730            cwd = opts["directory"]731            cmd = (732                [opts["clang"], "--analyze"]733                + opts["direct_args"]734                + opts["flags"]735                + [opts["file"]]736            )737            triarch = get_triple_arch(cmd, cwd)738            ctu_options = [739                "ctu-dir=" + os.path.join(ctu_config.dir, triarch),740                "experimental-enable-naive-ctu-analysis=true",741            ]742            analyzer_options = prefix_with("-analyzer-config", ctu_options)743            direct_options = prefix_with("-Xanalyzer", analyzer_options)744            opts["direct_args"].extend(direct_options)745 746    return continuation(opts)747 748 749@require(["flags", "force_debug"])750def filter_debug_flags(opts, continuation=dispatch_ctu):751    """Filter out nondebug macros when requested."""752 753    if opts.pop("force_debug"):754        # lazy implementation just append an undefine macro at the end755        opts.update({"flags": opts["flags"] + ["-UNDEBUG"]})756 757    return continuation(opts)758 759 760@require(["language", "compiler", "file", "flags"])761def language_check(opts, continuation=filter_debug_flags):762    """Find out the language from command line parameters or file name763    extension. The decision also influenced by the compiler invocation."""764 765    accepted = frozenset(766        {767            "c",768            "c++",769            "objective-c",770            "objective-c++",771            "c-cpp-output",772            "c++-cpp-output",773            "objective-c-cpp-output",774        }775    )776 777    # language can be given as a parameter...778    language = opts.pop("language")779    compiler = opts.pop("compiler")780    # ... or find out from source file extension781    if language is None and compiler is not None:782        language = classify_source(opts["file"], compiler == "c")783 784    if language is None:785        logging.debug("skip analysis, language not known")786        return None787    elif language not in accepted:788        logging.debug("skip analysis, language not supported")789        return None790    else:791        logging.debug("analysis, language: %s", language)792        opts.update({"language": language, "flags": ["-x", language] + opts["flags"]})793        return continuation(opts)794 795 796@require(["arch_list", "flags"])797def arch_check(opts, continuation=language_check):798    """Do run analyzer through one of the given architectures."""799 800    disabled = frozenset({"ppc", "ppc64"})801 802    received_list = opts.pop("arch_list")803    if received_list:804        # filter out disabled architectures and -arch switches805        filtered_list = [a for a in received_list if a not in disabled]806        if filtered_list:807            # There should be only one arch given (or the same multiple808            # times). If there are multiple arch are given and are not809            # the same, those should not change the pre-processing step.810            # But that's the only pass we have before run the analyzer.811            current = filtered_list.pop()812            logging.debug("analysis, on arch: %s", current)813 814            opts.update({"flags": ["-arch", current] + opts["flags"]})815            return continuation(opts)816        else:817            logging.debug("skip analysis, found not supported arch")818            return None819    else:820        logging.debug("analysis, on default arch")821        return continuation(opts)822 823 824# To have good results from static analyzer certain compiler options shall be825# omitted. The compiler flag filtering only affects the static analyzer run.826#827# Keys are the option name, value number of options to skip828IGNORED_FLAGS = {829    "-c": 0,  # compile option will be overwritten830    "-fsyntax-only": 0,  # static analyzer option will be overwritten831    "-o": 1,  # will set up own output file832    # flags below are inherited from the perl implementation.833    "-g": 0,834    "-save-temps": 0,835    "-install_name": 1,836    "-exported_symbols_list": 1,837    "-current_version": 1,838    "-compatibility_version": 1,839    "-init": 1,840    "-e": 1,841    "-seg1addr": 1,842    "-bundle_loader": 1,843    "-multiply_defined": 1,844    "-sectorder": 3,845    "--param": 1,846    "--serialize-diagnostics": 1,847}848 849 850def classify_parameters(command):851    """Prepare compiler flags (filters some and add others) and take out852    language (-x) and architecture (-arch) flags for future processing."""853 854    result = {855        "flags": [],  # the filtered compiler flags856        "arch_list": [],  # list of architecture flags857        "language": None,  # compilation language, None, if not specified858        "compiler": compiler_language(command),  # 'c' or 'c++'859    }860 861    # iterate on the compile options862    args = iter(command[1:])863    for arg in args:864        # take arch flags into a separate basket865        if arg == "-arch":866            result["arch_list"].append(next(args))867        # take language868        elif arg == "-x":869            result["language"] = next(args)870        # parameters which looks source file are not flags871        elif re.match(r"^[^-].+", arg) and classify_source(arg):872            pass873        # ignore some flags874        elif arg in IGNORED_FLAGS:875            count = IGNORED_FLAGS[arg]876            for _ in range(count):877                next(args)878        # we don't care about extra warnings, but we should suppress ones879        # that we don't want to see.880        elif re.match(r"^-W.+", arg) and not re.match(r"^-Wno-.+", arg):881            pass882        # and consider everything else as compilation flag.883        else:884            result["flags"].append(arg)885 886    return result887