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