brintos

brintos / llvm-project-archived public Read only

0
0
Text · 19.3 KiB · e9794e7 Raw
568 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 parses and validates arguments for command-line interfaces.6 7It uses argparse module to create the command line parser. (This library is8in the standard python library since 3.2 and backported to 2.7, but not9earlier.)10 11It also implements basic validation methods, related to the command.12Validations are mostly calling specific help methods, or mangling values.13"""14from __future__ import absolute_import, division, print_function15 16import os17import sys18import argparse19import logging20import tempfile21from libscanbuild import reconfigure_logging, CtuConfig22from libscanbuild.clang import get_checkers, is_ctu_capable23 24__all__ = [25    "parse_args_for_intercept_build",26    "parse_args_for_analyze_build",27    "parse_args_for_scan_build",28]29 30 31def parse_args_for_intercept_build():32    """Parse and validate command-line arguments for intercept-build."""33 34    parser = create_intercept_parser()35    args = parser.parse_args()36 37    reconfigure_logging(args.verbose)38    logging.debug("Raw arguments %s", sys.argv)39 40    # short validation logic41    if not args.build:42        parser.error(message="missing build command")43 44    logging.debug("Parsed arguments: %s", args)45    return args46 47 48def parse_args_for_analyze_build():49    """Parse and validate command-line arguments for analyze-build."""50 51    from_build_command = False52    parser = create_analyze_parser(from_build_command)53    args = parser.parse_args()54 55    reconfigure_logging(args.verbose)56    logging.debug("Raw arguments %s", sys.argv)57 58    normalize_args_for_analyze(args, from_build_command)59    validate_args_for_analyze(parser, args, from_build_command)60    logging.debug("Parsed arguments: %s", args)61    return args62 63 64def parse_args_for_scan_build():65    """Parse and validate command-line arguments for scan-build."""66 67    from_build_command = True68    parser = create_analyze_parser(from_build_command)69    args = parser.parse_args()70 71    reconfigure_logging(args.verbose)72    logging.debug("Raw arguments %s", sys.argv)73 74    normalize_args_for_analyze(args, from_build_command)75    validate_args_for_analyze(parser, args, from_build_command)76    logging.debug("Parsed arguments: %s", args)77    return args78 79 80def normalize_args_for_analyze(args, from_build_command):81    """Normalize parsed arguments for analyze-build and scan-build.82 83    :param args: Parsed argument object. (Will be mutated.)84    :param from_build_command: Boolean value tells is the command suppose85    to run the analyzer against a build command or a compilation db."""86 87    # make plugins always a list. (it might be None when not specified.)88    if args.plugins is None:89        args.plugins = []90 91    # make exclude directory list unique and absolute.92    uniq_excludes = set(os.path.abspath(entry) for entry in args.excludes)93    args.excludes = list(uniq_excludes)94 95    # because shared codes for all tools, some common used methods are96    # expecting some argument to be present. so, instead of query the args97    # object about the presence of the flag, we fake it here. to make those98    # methods more readable. (it's an arguable choice, took it only for those99    # which have good default value.)100    if from_build_command:101        # add cdb parameter invisibly to make report module working.102        args.cdb = "compile_commands.json"103 104    # Make ctu_dir an abspath as it is needed inside clang105    if (106        not from_build_command107        and hasattr(args, "ctu_phases")108        and hasattr(args.ctu_phases, "dir")109    ):110        args.ctu_dir = os.path.abspath(args.ctu_dir)111 112 113def validate_args_for_analyze(parser, args, from_build_command):114    """Command line parsing is done by the argparse module, but semantic115    validation still needs to be done. This method is doing it for116    analyze-build and scan-build commands.117 118    :param parser: The command line parser object.119    :param args: Parsed argument object.120    :param from_build_command: Boolean value tells is the command suppose121    to run the analyzer against a build command or a compilation db.122    :return: No return value, but this call might throw when validation123    fails."""124 125    if args.help_checkers_verbose:126        print_checkers(get_checkers(args.clang, args.plugins))127        parser.exit(status=0)128    elif args.help_checkers:129        print_active_checkers(get_checkers(args.clang, args.plugins))130        parser.exit(status=0)131    elif from_build_command and not args.build:132        parser.error(message="missing build command")133    elif not from_build_command and not os.path.exists(args.cdb):134        parser.error(message="compilation database is missing")135 136    # If the user wants CTU mode137    if (138        not from_build_command139        and hasattr(args, "ctu_phases")140        and hasattr(args.ctu_phases, "dir")141    ):142        # If CTU analyze_only, the input directory should exist143        if (144            args.ctu_phases.analyze145            and not args.ctu_phases.collect146            and not os.path.exists(args.ctu_dir)147        ):148            parser.error(message="missing CTU directory")149        # Check CTU capability via checking clang-extdef-mapping150        if not is_ctu_capable(args.extdef_map_cmd):151            parser.error(152                message="""This version of clang does not support CTU153            functionality or clang-extdef-mapping command not found."""154            )155 156 157def create_intercept_parser():158    """Creates a parser for command-line arguments to 'intercept'."""159 160    parser = create_default_parser()161    parser_add_cdb(parser)162 163    parser_add_prefer_wrapper(parser)164    parser_add_compilers(parser)165 166    advanced = parser.add_argument_group("advanced options")167    group = advanced.add_mutually_exclusive_group()168    group.add_argument(169        "--append",170        action="store_true",171        help="""Extend existing compilation database with new entries.172        Duplicate entries are detected and not present in the final output.173        The output is not continuously updated, it's done when the build174        command finished. """,175    )176 177    parser.add_argument(178        dest="build", nargs=argparse.REMAINDER, help="""Command to run."""179    )180    return parser181 182 183def create_analyze_parser(from_build_command):184    """Creates a parser for command-line arguments to 'analyze'."""185 186    parser = create_default_parser()187 188    if from_build_command:189        parser_add_prefer_wrapper(parser)190        parser_add_compilers(parser)191 192        parser.add_argument(193            "--intercept-first",194            action="store_true",195            help="""Run the build commands first, intercept compiler196            calls and then run the static analyzer afterwards.197            Generally speaking it has better coverage on build commands.198            With '--override-compiler' it use compiler wrapper, but does199            not run the analyzer till the build is finished.""",200        )201    else:202        parser_add_cdb(parser)203 204    parser.add_argument(205        "--status-bugs",206        action="store_true",207        help="""The exit status of '%(prog)s' is the same as the executed208        build command. This option ignores the build exit status and sets to209        be non zero if it found potential bugs or zero otherwise.""",210    )211    parser.add_argument(212        "--exclude",213        metavar="<directory>",214        dest="excludes",215        action="append",216        default=[],217        help="""Do not run static analyzer against files found in this218        directory. (You can specify this option multiple times.)219        Could be useful when project contains 3rd party libraries.""",220    )221 222    output = parser.add_argument_group("output control options")223    output.add_argument(224        "--output",225        "-o",226        metavar="<path>",227        default=tempfile.gettempdir(),228        help="""Specifies the output directory for analyzer reports.229        Subdirectory will be created if default directory is targeted.""",230    )231    output.add_argument(232        "--keep-empty",233        action="store_true",234        help="""Don't remove the build results directory even if no issues235        were reported.""",236    )237    output.add_argument(238        "--html-title",239        metavar="<title>",240        help="""Specify the title used on generated HTML pages.241        If not specified, a default title will be used.""",242    )243    format_group = output.add_mutually_exclusive_group()244    format_group.add_argument(245        "--plist",246        "-plist",247        dest="output_format",248        const="plist",249        default="html",250        action="store_const",251        help="""Cause the results as a set of .plist files.""",252    )253    format_group.add_argument(254        "--plist-html",255        "-plist-html",256        dest="output_format",257        const="plist-html",258        default="html",259        action="store_const",260        help="""Cause the results as a set of .html and .plist files.""",261    )262    format_group.add_argument(263        "--plist-multi-file",264        "-plist-multi-file",265        dest="output_format",266        const="plist-multi-file",267        default="html",268        action="store_const",269        help="""Cause the results as a set of .plist files with extra270        information on related files.""",271    )272    format_group.add_argument(273        "--sarif",274        "-sarif",275        dest="output_format",276        const="sarif",277        default="html",278        action="store_const",279        help="""Cause the results as a result.sarif file.""",280    )281    format_group.add_argument(282        "--sarif-html",283        "-sarif-html",284        dest="output_format",285        const="sarif-html",286        default="html",287        action="store_const",288        help="""Cause the results as a result.sarif file and .html files.""",289    )290 291    advanced = parser.add_argument_group("advanced options")292    advanced.add_argument(293        "--use-analyzer",294        metavar="<path>",295        dest="clang",296        default="clang",297        help="""'%(prog)s' uses the 'clang' executable relative to itself for298        static analysis. One can override this behavior with this option by299        using the 'clang' packaged with Xcode (on OS X) or from the PATH.""",300    )301    advanced.add_argument(302        "--no-failure-reports",303        "-no-failure-reports",304        dest="output_failures",305        action="store_false",306        help="""Do not create a 'failures' subdirectory that includes analyzer307        crash reports and preprocessed source files.""",308    )309    parser.add_argument(310        "--analyze-headers",311        action="store_true",312        help="""Also analyze functions in #included files. By default, such313        functions are skipped unless they are called by functions within the314        main source file.""",315    )316    advanced.add_argument(317        "--stats",318        "-stats",319        action="store_true",320        help="""Generates visitation statistics for the project.""",321    )322    advanced.add_argument(323        "--internal-stats",324        action="store_true",325        help="""Generate internal analyzer statistics.""",326    )327    advanced.add_argument(328        "--maxloop",329        "-maxloop",330        metavar="<loop count>",331        type=int,332        help="""Specify the number of times a block can be visited before333        giving up. Increase for more comprehensive coverage at a cost of334        speed.""",335    )336    advanced.add_argument(337        "--store",338        "-store",339        metavar="<model>",340        dest="store_model",341        choices=["region", "basic"],342        help="""Specify the store model used by the analyzer. 'region'343        specifies a field- sensitive store model. 'basic' which is far less344        precise but can more quickly analyze code. 'basic' was the default345        store model for checker-0.221 and earlier.""",346    )347    advanced.add_argument(348        "--constraints",349        "-constraints",350        metavar="<model>",351        dest="constraints_model",352        choices=["range", "basic"],353        help="""Specify the constraint engine used by the analyzer. Specifying354        'basic' uses a simpler, less powerful constraint model used by355        checker-0.160 and earlier.""",356    )357    advanced.add_argument(358        "--analyzer-config",359        "-analyzer-config",360        metavar="<options>",361        help="""Provide options to pass through to the analyzer's362        -analyzer-config flag. Several options are separated with comma:363        'key1=val1,key2=val2'364 365        Available options:366            stable-report-filename=true or false (default)367 368        Switch the page naming to:369        report-<filename>-<function/method name>-<id>.html370        instead of report-XXXXXX.html""",371    )372    advanced.add_argument(373        "--force-analyze-debug-code",374        dest="force_debug",375        action="store_true",376        help="""Tells analyzer to enable assertions in code even if they were377        disabled during compilation, enabling more precise results.""",378    )379 380    plugins = parser.add_argument_group("checker options")381    plugins.add_argument(382        "--load-plugin",383        "-load-plugin",384        metavar="<plugin library>",385        dest="plugins",386        action="append",387        help="""Loading external checkers using the clang plugin interface.""",388    )389    plugins.add_argument(390        "--enable-checker",391        "-enable-checker",392        metavar="<checker name>",393        action=AppendCommaSeparated,394        help="""Enable specific checker.""",395    )396    plugins.add_argument(397        "--disable-checker",398        "-disable-checker",399        metavar="<checker name>",400        action=AppendCommaSeparated,401        help="""Disable specific checker.""",402    )403    plugins.add_argument(404        "--help-checkers",405        action="store_true",406        help="""A default group of checkers is run unless explicitly disabled.407        Exactly which checkers constitute the default group is a function of408        the operating system in use. These can be printed with this flag.""",409    )410    plugins.add_argument(411        "--help-checkers-verbose",412        action="store_true",413        help="""Print all available checkers and mark the enabled ones.""",414    )415 416    if from_build_command:417        parser.add_argument(418            dest="build", nargs=argparse.REMAINDER, help="""Command to run."""419        )420    else:421        ctu = parser.add_argument_group("cross translation unit analysis")422        ctu_mutex_group = ctu.add_mutually_exclusive_group()423        ctu_mutex_group.add_argument(424            "--ctu",425            action="store_const",426            const=CtuConfig(collect=True, analyze=True, dir="", extdef_map_cmd=""),427            dest="ctu_phases",428            help="""Perform cross translation unit (ctu) analysis (both collect429            and analyze phases) using default <ctu-dir> for temporary output.430            At the end of the analysis, the temporary directory is removed.""",431        )432        ctu.add_argument(433            "--ctu-dir",434            metavar="<ctu-dir>",435            dest="ctu_dir",436            default="ctu-dir",437            help="""Defines the temporary directory used between ctu438            phases.""",439        )440        ctu_mutex_group.add_argument(441            "--ctu-collect-only",442            action="store_const",443            const=CtuConfig(collect=True, analyze=False, dir="", extdef_map_cmd=""),444            dest="ctu_phases",445            help="""Perform only the collect phase of ctu.446            Keep <ctu-dir> for further use.""",447        )448        ctu_mutex_group.add_argument(449            "--ctu-analyze-only",450            action="store_const",451            const=CtuConfig(collect=False, analyze=True, dir="", extdef_map_cmd=""),452            dest="ctu_phases",453            help="""Perform only the analyze phase of ctu. <ctu-dir> should be454            present and will not be removed after analysis.""",455        )456        ctu.add_argument(457            "--use-extdef-map-cmd",458            metavar="<path>",459            dest="extdef_map_cmd",460            default="clang-extdef-mapping",461            help="""'%(prog)s' uses the 'clang-extdef-mapping' executable462            relative to itself for generating external definition maps for463            static analysis. One can override this behavior with this option464            by using the 'clang-extdef-mapping' packaged with Xcode (on OS X)465            or from the PATH.""",466        )467    return parser468 469 470def create_default_parser():471    """Creates command line parser for all build wrapper commands."""472 473    parser = argparse.ArgumentParser(474        formatter_class=argparse.ArgumentDefaultsHelpFormatter475    )476 477    parser.add_argument(478        "--verbose",479        "-v",480        action="count",481        default=0,482        help="""Enable verbose output from '%(prog)s'. A second, third and483        fourth flags increases verbosity.""",484    )485    return parser486 487 488def parser_add_cdb(parser):489    parser.add_argument(490        "--cdb",491        metavar="<file>",492        default="compile_commands.json",493        help="""The JSON compilation database.""",494    )495 496 497def parser_add_prefer_wrapper(parser):498    parser.add_argument(499        "--override-compiler",500        action="store_true",501        help="""Always resort to the compiler wrapper even when better502        intercept methods are available.""",503    )504 505 506def parser_add_compilers(parser):507    parser.add_argument(508        "--use-cc",509        metavar="<path>",510        dest="cc",511        default=os.getenv("CC", "cc"),512        help="""When '%(prog)s' analyzes a project by interposing a compiler513        wrapper, which executes a real compiler for compilation and do other514        tasks (record the compiler invocation). Because of this interposing,515        '%(prog)s' does not know what compiler your project normally uses.516        Instead, it simply overrides the CC environment variable, and guesses517        your default compiler.518 519        If you need '%(prog)s' to use a specific compiler for *compilation*520        then you can use this option to specify a path to that compiler.""",521    )522    parser.add_argument(523        "--use-c++",524        metavar="<path>",525        dest="cxx",526        default=os.getenv("CXX", "c++"),527        help="""This is the same as "--use-cc" but for C++ code.""",528    )529 530 531class AppendCommaSeparated(argparse.Action):532    """argparse Action class to support multiple comma separated lists."""533 534    def __call__(self, __parser, namespace, values, __option_string):535        # getattr(obj, attr, default) does not really returns default but none536        if getattr(namespace, self.dest, None) is None:537            setattr(namespace, self.dest, [])538        # once it's fixed we can use as expected539        actual = getattr(namespace, self.dest)540        actual.extend(values.split(","))541        setattr(namespace, self.dest, actual)542 543 544def print_active_checkers(checkers):545    """Print active checkers to stdout."""546 547    for name in sorted(name for name, (_, active) in checkers.items() if active):548        print(name)549 550 551def print_checkers(checkers):552    """Print verbose checker help to stdout."""553 554    print("")555    print("available checkers:")556    print("")557    for name in sorted(checkers.keys()):558        description, active = checkers[name]559        prefix = "+" if active else " "560        if len(name) > 30:561            print(" {0} {1}".format(prefix, name))562            print(" " * 35 + description)563        else:564            print(" {0} {1: <30}  {2}".format(prefix, name, description))565    print("")566    print('NOTE: "+" indicates that an analysis is enabled by default.')567    print("")568