- # -*- 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 parses and validates arguments for command-line interfaces. 
-   
- It uses argparse module to create the command line parser. (This library is 
- in the standard python library since 3.2 and backported to 2.7, but not 
- earlier.) 
-   
- It also implements basic validation methods, related to the command. 
- Validations are mostly calling specific help methods, or mangling values. 
- """ 
- from __future__ import absolute_import, division, print_function 
-   
- import os 
- import sys 
- import argparse 
- import logging 
- import tempfile 
- from libscanbuild import reconfigure_logging, CtuConfig 
- from libscanbuild.clang import get_checkers, is_ctu_capable 
-   
- __all__ = ['parse_args_for_intercept_build', 'parse_args_for_analyze_build', 
-            'parse_args_for_scan_build'] 
-   
-   
- def parse_args_for_intercept_build(): 
-     """ Parse and validate command-line arguments for intercept-build. """ 
-   
-     parser = create_intercept_parser() 
-     args = parser.parse_args() 
-   
-     reconfigure_logging(args.verbose) 
-     logging.debug('Raw arguments %s', sys.argv) 
-   
-     # short validation logic 
-     if not args.build: 
-         parser.error(message='missing build command') 
-   
-     logging.debug('Parsed arguments: %s', args) 
-     return args 
-   
-   
- def parse_args_for_analyze_build(): 
-     """ Parse and validate command-line arguments for analyze-build. """ 
-   
-     from_build_command = False 
-     parser = create_analyze_parser(from_build_command) 
-     args = parser.parse_args() 
-   
-     reconfigure_logging(args.verbose) 
-     logging.debug('Raw arguments %s', sys.argv) 
-   
-     normalize_args_for_analyze(args, from_build_command) 
-     validate_args_for_analyze(parser, args, from_build_command) 
-     logging.debug('Parsed arguments: %s', args) 
-     return args 
-   
-   
- def parse_args_for_scan_build(): 
-     """ Parse and validate command-line arguments for scan-build. """ 
-   
-     from_build_command = True 
-     parser = create_analyze_parser(from_build_command) 
-     args = parser.parse_args() 
-   
-     reconfigure_logging(args.verbose) 
-     logging.debug('Raw arguments %s', sys.argv) 
-   
-     normalize_args_for_analyze(args, from_build_command) 
-     validate_args_for_analyze(parser, args, from_build_command) 
-     logging.debug('Parsed arguments: %s', args) 
-     return args 
-   
-   
- def normalize_args_for_analyze(args, from_build_command): 
-     """ Normalize parsed arguments for analyze-build and scan-build. 
-   
-     :param args: Parsed argument object. (Will be mutated.) 
-     :param from_build_command: Boolean value tells is the command suppose 
-     to run the analyzer against a build command or a compilation db. """ 
-   
-     # make plugins always a list. (it might be None when not specified.) 
-     if args.plugins is None: 
-         args.plugins = [] 
-   
-     # make exclude directory list unique and absolute. 
-     uniq_excludes = set(os.path.abspath(entry) for entry in args.excludes) 
-     args.excludes = list(uniq_excludes) 
-   
-     # because shared codes for all tools, some common used methods are 
-     # expecting some argument to be present. so, instead of query the args 
-     # object about the presence of the flag, we fake it here. to make those 
-     # methods more readable. (it's an arguable choice, took it only for those 
-     # which have good default value.) 
-     if from_build_command: 
-         # add cdb parameter invisibly to make report module working. 
-         args.cdb = 'compile_commands.json' 
-   
-     # Make ctu_dir an abspath as it is needed inside clang 
-     if not from_build_command and hasattr(args, 'ctu_phases') \ 
-             and hasattr(args.ctu_phases, 'dir'): 
-         args.ctu_dir = os.path.abspath(args.ctu_dir) 
-   
-   
- def validate_args_for_analyze(parser, args, from_build_command): 
-     """ Command line parsing is done by the argparse module, but semantic 
-     validation still needs to be done. This method is doing it for 
-     analyze-build and scan-build commands. 
-   
-     :param parser: The command line parser object. 
-     :param args: Parsed argument object. 
-     :param from_build_command: Boolean value tells is the command suppose 
-     to run the analyzer against a build command or a compilation db. 
-     :return: No return value, but this call might throw when validation 
-     fails. """ 
-   
-     if args.help_checkers_verbose: 
-         print_checkers(get_checkers(args.clang, args.plugins)) 
-         parser.exit(status=0) 
-     elif args.help_checkers: 
-         print_active_checkers(get_checkers(args.clang, args.plugins)) 
-         parser.exit(status=0) 
-     elif from_build_command and not args.build: 
-         parser.error(message='missing build command') 
-     elif not from_build_command and not os.path.exists(args.cdb): 
-         parser.error(message='compilation database is missing') 
-   
-     # If the user wants CTU mode 
-     if not from_build_command and hasattr(args, 'ctu_phases') \ 
-             and hasattr(args.ctu_phases, 'dir'): 
-         # If CTU analyze_only, the input directory should exist 
-         if args.ctu_phases.analyze and not args.ctu_phases.collect \ 
-                 and not os.path.exists(args.ctu_dir): 
-             parser.error(message='missing CTU directory') 
-         # Check CTU capability via checking clang-extdef-mapping 
-         if not is_ctu_capable(args.extdef_map_cmd): 
-             parser.error(message="""This version of clang does not support CTU 
-             functionality or clang-extdef-mapping command not found.""") 
-   
-   
- def create_intercept_parser(): 
-     """ Creates a parser for command-line arguments to 'intercept'. """ 
-   
-     parser = create_default_parser() 
-     parser_add_cdb(parser) 
-   
-     parser_add_prefer_wrapper(parser) 
-     parser_add_compilers(parser) 
-   
-     advanced = parser.add_argument_group('advanced options') 
-     group = advanced.add_mutually_exclusive_group() 
-     group.add_argument( 
-         '--append', 
-         action='store_true', 
-         help="""Extend existing compilation database with new entries. 
-         Duplicate entries are detected and not present in the final output. 
-         The output is not continuously updated, it's done when the build 
-         command finished. """) 
-   
-     parser.add_argument( 
-         dest='build', nargs=argparse.REMAINDER, help="""Command to run.""") 
-     return parser 
-   
-   
- def create_analyze_parser(from_build_command): 
-     """ Creates a parser for command-line arguments to 'analyze'. """ 
-   
-     parser = create_default_parser() 
-   
-     if from_build_command: 
-         parser_add_prefer_wrapper(parser) 
-         parser_add_compilers(parser) 
-   
-         parser.add_argument( 
-             '--intercept-first', 
-             action='store_true', 
-             help="""Run the build commands first, intercept compiler 
-             calls and then run the static analyzer afterwards. 
-             Generally speaking it has better coverage on build commands. 
-             With '--override-compiler' it use compiler wrapper, but does 
-             not run the analyzer till the build is finished.""") 
-     else: 
-         parser_add_cdb(parser) 
-   
-     parser.add_argument( 
-         '--status-bugs', 
-         action='store_true', 
-         help="""The exit status of '%(prog)s' is the same as the executed 
-         build command. This option ignores the build exit status and sets to 
-         be non zero if it found potential bugs or zero otherwise.""") 
-     parser.add_argument( 
-         '--exclude', 
-         metavar='<directory>', 
-         dest='excludes', 
-         action='append', 
-         default=[], 
-         help="""Do not run static analyzer against files found in this 
-         directory. (You can specify this option multiple times.) 
-         Could be useful when project contains 3rd party libraries.""") 
-   
-     output = parser.add_argument_group('output control options') 
-     output.add_argument( 
-         '--output', 
-         '-o', 
-         metavar='<path>', 
-         default=tempfile.gettempdir(), 
-         help="""Specifies the output directory for analyzer reports. 
-         Subdirectory will be created if default directory is targeted.""") 
-     output.add_argument( 
-         '--keep-empty', 
-         action='store_true', 
-         help="""Don't remove the build results directory even if no issues 
-         were reported.""") 
-     output.add_argument( 
-         '--html-title', 
-         metavar='<title>', 
-         help="""Specify the title used on generated HTML pages. 
-         If not specified, a default title will be used.""") 
-     format_group = output.add_mutually_exclusive_group() 
-     format_group.add_argument( 
-         '--plist', 
-         '-plist', 
-         dest='output_format', 
-         const='plist', 
-         default='html', 
-         action='store_const', 
-         help="""Cause the results as a set of .plist files.""") 
-     format_group.add_argument( 
-         '--plist-html', 
-         '-plist-html', 
-         dest='output_format', 
-         const='plist-html', 
-         default='html', 
-         action='store_const', 
-         help="""Cause the results as a set of .html and .plist files.""") 
-     format_group.add_argument( 
-         '--plist-multi-file', 
-         '-plist-multi-file', 
-         dest='output_format', 
-         const='plist-multi-file', 
-         default='html', 
-         action='store_const', 
-         help="""Cause the results as a set of .plist files with extra 
-         information on related files.""") 
-     format_group.add_argument( 
-         '--sarif', 
-         '-sarif', 
-         dest='output_format', 
-         const='sarif', 
-         default='html', 
-         action='store_const', 
-         help="""Cause the results as a result.sarif file.""") 
-     format_group.add_argument( 
-         '--sarif-html', 
-         '-sarif-html', 
-         dest='output_format', 
-         const='sarif-html', 
-         default='html', 
-         action='store_const', 
-         help="""Cause the results as a result.sarif file and .html files.""") 
-   
-     advanced = parser.add_argument_group('advanced options') 
-     advanced.add_argument( 
-         '--use-analyzer', 
-         metavar='<path>', 
-         dest='clang', 
-         default='clang', 
-         help="""'%(prog)s' uses the 'clang' executable relative to itself for 
-         static analysis. One can override this behavior with this option by 
-         using the 'clang' packaged with Xcode (on OS X) or from the PATH.""") 
-     advanced.add_argument( 
-         '--no-failure-reports', 
-         '-no-failure-reports', 
-         dest='output_failures', 
-         action='store_false', 
-         help="""Do not create a 'failures' subdirectory that includes analyzer 
-         crash reports and preprocessed source files.""") 
-     parser.add_argument( 
-         '--analyze-headers', 
-         action='store_true', 
-         help="""Also analyze functions in #included files. By default, such 
-         functions are skipped unless they are called by functions within the 
-         main source file.""") 
-     advanced.add_argument( 
-         '--stats', 
-         '-stats', 
-         action='store_true', 
-         help="""Generates visitation statistics for the project.""") 
-     advanced.add_argument( 
-         '--internal-stats', 
-         action='store_true', 
-         help="""Generate internal analyzer statistics.""") 
-     advanced.add_argument( 
-         '--maxloop', 
-         '-maxloop', 
-         metavar='<loop count>', 
-         type=int, 
-         help="""Specify the number of times a block can be visited before 
-         giving up. Increase for more comprehensive coverage at a cost of 
-         speed.""") 
-     advanced.add_argument( 
-         '--store', 
-         '-store', 
-         metavar='<model>', 
-         dest='store_model', 
-         choices=['region', 'basic'], 
-         help="""Specify the store model used by the analyzer. 'region' 
-         specifies a field- sensitive store model. 'basic' which is far less 
-         precise but can more quickly analyze code. 'basic' was the default 
-         store model for checker-0.221 and earlier.""") 
-     advanced.add_argument( 
-         '--constraints', 
-         '-constraints', 
-         metavar='<model>', 
-         dest='constraints_model', 
-         choices=['range', 'basic'], 
-         help="""Specify the constraint engine used by the analyzer. Specifying 
-         'basic' uses a simpler, less powerful constraint model used by 
-         checker-0.160 and earlier.""") 
-     advanced.add_argument( 
-         '--analyzer-config', 
-         '-analyzer-config', 
-         metavar='<options>', 
-         help="""Provide options to pass through to the analyzer's 
-         -analyzer-config flag. Several options are separated with comma: 
-         'key1=val1,key2=val2' 
-   
-         Available options: 
-             stable-report-filename=true or false (default) 
-   
-         Switch the page naming to: 
-         report-<filename>-<function/method name>-<id>.html 
-         instead of report-XXXXXX.html""") 
-     advanced.add_argument( 
-         '--force-analyze-debug-code', 
-         dest='force_debug', 
-         action='store_true', 
-         help="""Tells analyzer to enable assertions in code even if they were 
-         disabled during compilation, enabling more precise results.""") 
-   
-     plugins = parser.add_argument_group('checker options') 
-     plugins.add_argument( 
-         '--load-plugin', 
-         '-load-plugin', 
-         metavar='<plugin library>', 
-         dest='plugins', 
-         action='append', 
-         help="""Loading external checkers using the clang plugin interface.""") 
-     plugins.add_argument( 
-         '--enable-checker', 
-         '-enable-checker', 
-         metavar='<checker name>', 
-         action=AppendCommaSeparated, 
-         help="""Enable specific checker.""") 
-     plugins.add_argument( 
-         '--disable-checker', 
-         '-disable-checker', 
-         metavar='<checker name>', 
-         action=AppendCommaSeparated, 
-         help="""Disable specific checker.""") 
-     plugins.add_argument( 
-         '--help-checkers', 
-         action='store_true', 
-         help="""A default group of checkers is run unless explicitly disabled. 
-         Exactly which checkers constitute the default group is a function of 
-         the operating system in use. These can be printed with this flag.""") 
-     plugins.add_argument( 
-         '--help-checkers-verbose', 
-         action='store_true', 
-         help="""Print all available checkers and mark the enabled ones.""") 
-   
-     if from_build_command: 
-         parser.add_argument( 
-             dest='build', nargs=argparse.REMAINDER, help="""Command to run.""") 
-     else: 
-         ctu = parser.add_argument_group('cross translation unit analysis') 
-         ctu_mutex_group = ctu.add_mutually_exclusive_group() 
-         ctu_mutex_group.add_argument( 
-             '--ctu', 
-             action='store_const', 
-             const=CtuConfig(collect=True, analyze=True, 
-                             dir='', extdef_map_cmd=''), 
-             dest='ctu_phases', 
-             help="""Perform cross translation unit (ctu) analysis (both collect 
-             and analyze phases) using default <ctu-dir> for temporary output. 
-             At the end of the analysis, the temporary directory is removed.""") 
-         ctu.add_argument( 
-             '--ctu-dir', 
-             metavar='<ctu-dir>', 
-             dest='ctu_dir', 
-             default='ctu-dir', 
-             help="""Defines the temporary directory used between ctu 
-             phases.""") 
-         ctu_mutex_group.add_argument( 
-             '--ctu-collect-only', 
-             action='store_const', 
-             const=CtuConfig(collect=True, analyze=False, 
-                             dir='', extdef_map_cmd=''), 
-             dest='ctu_phases', 
-             help="""Perform only the collect phase of ctu. 
-             Keep <ctu-dir> for further use.""") 
-         ctu_mutex_group.add_argument( 
-             '--ctu-analyze-only', 
-             action='store_const', 
-             const=CtuConfig(collect=False, analyze=True, 
-                             dir='', extdef_map_cmd=''), 
-             dest='ctu_phases', 
-             help="""Perform only the analyze phase of ctu. <ctu-dir> should be 
-             present and will not be removed after analysis.""") 
-         ctu.add_argument( 
-             '--use-extdef-map-cmd', 
-             metavar='<path>', 
-             dest='extdef_map_cmd', 
-             default='clang-extdef-mapping', 
-             help="""'%(prog)s' uses the 'clang-extdef-mapping' executable 
-             relative to itself for generating external definition maps for 
-             static analysis. One can override this behavior with this option 
-             by using the 'clang-extdef-mapping' packaged with Xcode (on OS X) 
-             or from the PATH.""") 
-     return parser 
-   
-   
- def create_default_parser(): 
-     """ Creates command line parser for all build wrapper commands. """ 
-   
-     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, third and 
-         fourth flags increases verbosity.""") 
-     return parser 
-   
-   
- def parser_add_cdb(parser): 
-     parser.add_argument( 
-         '--cdb', 
-         metavar='<file>', 
-         default="compile_commands.json", 
-         help="""The JSON compilation database.""") 
-   
-   
- def parser_add_prefer_wrapper(parser): 
-     parser.add_argument( 
-         '--override-compiler', 
-         action='store_true', 
-         help="""Always resort to the compiler wrapper even when better 
-         intercept methods are available.""") 
-   
-   
- def parser_add_compilers(parser): 
-     parser.add_argument( 
-         '--use-cc', 
-         metavar='<path>', 
-         dest='cc', 
-         default=os.getenv('CC', '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.""") 
-     parser.add_argument( 
-         '--use-c++', 
-         metavar='<path>', 
-         dest='cxx', 
-         default=os.getenv('CXX', 'c++'), 
-         help="""This is the same as "--use-cc" but for C++ code.""") 
-   
-   
- class AppendCommaSeparated(argparse.Action): 
-     """ argparse Action class to support multiple comma separated lists. """ 
-   
-     def __call__(self, __parser, namespace, values, __option_string): 
-         # getattr(obj, attr, default) does not really returns default but none 
-         if getattr(namespace, self.dest, None) is None: 
-             setattr(namespace, self.dest, []) 
-         # once it's fixed we can use as expected 
-         actual = getattr(namespace, self.dest) 
-         actual.extend(values.split(',')) 
-         setattr(namespace, self.dest, actual) 
-   
-   
- def print_active_checkers(checkers): 
-     """ Print active checkers to stdout. """ 
-   
-     for name in sorted(name for name, (_, active) in checkers.items() 
-                        if active): 
-         print(name) 
-   
-   
- def print_checkers(checkers): 
-     """ Print verbose checker help to stdout. """ 
-   
-     print('') 
-     print('available checkers:') 
-     print('') 
-     for name in sorted(checkers.keys()): 
-         description, active = checkers[name] 
-         prefix = '+' if active else ' ' 
-         if len(name) > 30: 
-             print(' {0} {1}'.format(prefix, name)) 
-             print(' ' * 35 + description) 
-         else: 
-             print(' {0} {1: <30}  {2}'.format(prefix, name, description)) 
-     print('') 
-     print('NOTE: "+" indicates that an analysis is enabled by default.') 
-     print('') 
-