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 format_group.add_argument( 248 '--sarif', 249 '-sarif', 250 dest='output_format', 251 const='sarif', 252 default='html', 253 action='store_const', 254 help="""Cause the results as a result.sarif file.""") 255 format_group.add_argument( 256 '--sarif-html', 257 '-sarif-html', 258 dest='output_format', 259 const='sarif-html', 260 default='html', 261 action='store_const', 262 help="""Cause the results as a result.sarif file and .html files.""") 263 264 advanced = parser.add_argument_group('advanced options') 265 advanced.add_argument( 266 '--use-analyzer', 267 metavar='<path>', 268 dest='clang', 269 default='clang', 270 help="""'%(prog)s' uses the 'clang' executable relative to itself for 271 static analysis. One can override this behavior with this option by 272 using the 'clang' packaged with Xcode (on OS X) or from the PATH.""") 273 advanced.add_argument( 274 '--no-failure-reports', 275 '-no-failure-reports', 276 dest='output_failures', 277 action='store_false', 278 help="""Do not create a 'failures' subdirectory that includes analyzer 279 crash reports and preprocessed source files.""") 280 parser.add_argument( 281 '--analyze-headers', 282 action='store_true', 283 help="""Also analyze functions in #included files. By default, such 284 functions are skipped unless they are called by functions within the 285 main source file.""") 286 advanced.add_argument( 287 '--stats', 288 '-stats', 289 action='store_true', 290 help="""Generates visitation statistics for the project.""") 291 advanced.add_argument( 292 '--internal-stats', 293 action='store_true', 294 help="""Generate internal analyzer statistics.""") 295 advanced.add_argument( 296 '--maxloop', 297 '-maxloop', 298 metavar='<loop count>', 299 type=int, 300 help="""Specify the number of times a block can be visited before 301 giving up. Increase for more comprehensive coverage at a cost of 302 speed.""") 303 advanced.add_argument( 304 '--store', 305 '-store', 306 metavar='<model>', 307 dest='store_model', 308 choices=['region', 'basic'], 309 help="""Specify the store model used by the analyzer. 'region' 310 specifies a field- sensitive store model. 'basic' which is far less 311 precise but can more quickly analyze code. 'basic' was the default 312 store model for checker-0.221 and earlier.""") 313 advanced.add_argument( 314 '--constraints', 315 '-constraints', 316 metavar='<model>', 317 dest='constraints_model', 318 choices=['range', 'basic'], 319 help="""Specify the constraint engine used by the analyzer. Specifying 320 'basic' uses a simpler, less powerful constraint model used by 321 checker-0.160 and earlier.""") 322 advanced.add_argument( 323 '--analyzer-config', 324 '-analyzer-config', 325 metavar='<options>', 326 help="""Provide options to pass through to the analyzer's 327 -analyzer-config flag. Several options are separated with comma: 328 'key1=val1,key2=val2' 329 330 Available options: 331 stable-report-filename=true or false (default) 332 333 Switch the page naming to: 334 report-<filename>-<function/method name>-<id>.html 335 instead of report-XXXXXX.html""") 336 advanced.add_argument( 337 '--force-analyze-debug-code', 338 dest='force_debug', 339 action='store_true', 340 help="""Tells analyzer to enable assertions in code even if they were 341 disabled during compilation, enabling more precise results.""") 342 343 plugins = parser.add_argument_group('checker options') 344 plugins.add_argument( 345 '--load-plugin', 346 '-load-plugin', 347 metavar='<plugin library>', 348 dest='plugins', 349 action='append', 350 help="""Loading external checkers using the clang plugin interface.""") 351 plugins.add_argument( 352 '--enable-checker', 353 '-enable-checker', 354 metavar='<checker name>', 355 action=AppendCommaSeparated, 356 help="""Enable specific checker.""") 357 plugins.add_argument( 358 '--disable-checker', 359 '-disable-checker', 360 metavar='<checker name>', 361 action=AppendCommaSeparated, 362 help="""Disable specific checker.""") 363 plugins.add_argument( 364 '--help-checkers', 365 action='store_true', 366 help="""A default group of checkers is run unless explicitly disabled. 367 Exactly which checkers constitute the default group is a function of 368 the operating system in use. These can be printed with this flag.""") 369 plugins.add_argument( 370 '--help-checkers-verbose', 371 action='store_true', 372 help="""Print all available checkers and mark the enabled ones.""") 373 374 if from_build_command: 375 parser.add_argument( 376 dest='build', nargs=argparse.REMAINDER, help="""Command to run.""") 377 else: 378 ctu = parser.add_argument_group('cross translation unit analysis') 379 ctu_mutex_group = ctu.add_mutually_exclusive_group() 380 ctu_mutex_group.add_argument( 381 '--ctu', 382 action='store_const', 383 const=CtuConfig(collect=True, analyze=True, 384 dir='', extdef_map_cmd=''), 385 dest='ctu_phases', 386 help="""Perform cross translation unit (ctu) analysis (both collect 387 and analyze phases) using default <ctu-dir> for temporary output. 388 At the end of the analysis, the temporary directory is removed.""") 389 ctu.add_argument( 390 '--ctu-dir', 391 metavar='<ctu-dir>', 392 dest='ctu_dir', 393 default='ctu-dir', 394 help="""Defines the temporary directory used between ctu 395 phases.""") 396 ctu_mutex_group.add_argument( 397 '--ctu-collect-only', 398 action='store_const', 399 const=CtuConfig(collect=True, analyze=False, 400 dir='', extdef_map_cmd=''), 401 dest='ctu_phases', 402 help="""Perform only the collect phase of ctu. 403 Keep <ctu-dir> for further use.""") 404 ctu_mutex_group.add_argument( 405 '--ctu-analyze-only', 406 action='store_const', 407 const=CtuConfig(collect=False, analyze=True, 408 dir='', extdef_map_cmd=''), 409 dest='ctu_phases', 410 help="""Perform only the analyze phase of ctu. <ctu-dir> should be 411 present and will not be removed after analysis.""") 412 ctu.add_argument( 413 '--use-extdef-map-cmd', 414 metavar='<path>', 415 dest='extdef_map_cmd', 416 default='clang-extdef-mapping', 417 help="""'%(prog)s' uses the 'clang-extdef-mapping' executable 418 relative to itself for generating external definition maps for 419 static analysis. One can override this behavior with this option 420 by using the 'clang-extdef-mapping' packaged with Xcode (on OS X) 421 or from the PATH.""") 422 return parser 423 424 425def create_default_parser(): 426 """ Creates command line parser for all build wrapper commands. """ 427 428 parser = argparse.ArgumentParser( 429 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 430 431 parser.add_argument( 432 '--verbose', 433 '-v', 434 action='count', 435 default=0, 436 help="""Enable verbose output from '%(prog)s'. A second, third and 437 fourth flags increases verbosity.""") 438 return parser 439 440 441def parser_add_cdb(parser): 442 parser.add_argument( 443 '--cdb', 444 metavar='<file>', 445 default="compile_commands.json", 446 help="""The JSON compilation database.""") 447 448 449def parser_add_prefer_wrapper(parser): 450 parser.add_argument( 451 '--override-compiler', 452 action='store_true', 453 help="""Always resort to the compiler wrapper even when better 454 intercept methods are available.""") 455 456 457def parser_add_compilers(parser): 458 parser.add_argument( 459 '--use-cc', 460 metavar='<path>', 461 dest='cc', 462 default=os.getenv('CC', 'cc'), 463 help="""When '%(prog)s' analyzes a project by interposing a compiler 464 wrapper, which executes a real compiler for compilation and do other 465 tasks (record the compiler invocation). Because of this interposing, 466 '%(prog)s' does not know what compiler your project normally uses. 467 Instead, it simply overrides the CC environment variable, and guesses 468 your default compiler. 469 470 If you need '%(prog)s' to use a specific compiler for *compilation* 471 then you can use this option to specify a path to that compiler.""") 472 parser.add_argument( 473 '--use-c++', 474 metavar='<path>', 475 dest='cxx', 476 default=os.getenv('CXX', 'c++'), 477 help="""This is the same as "--use-cc" but for C++ code.""") 478 479 480class AppendCommaSeparated(argparse.Action): 481 """ argparse Action class to support multiple comma separated lists. """ 482 483 def __call__(self, __parser, namespace, values, __option_string): 484 # getattr(obj, attr, default) does not really returns default but none 485 if getattr(namespace, self.dest, None) is None: 486 setattr(namespace, self.dest, []) 487 # once it's fixed we can use as expected 488 actual = getattr(namespace, self.dest) 489 actual.extend(values.split(',')) 490 setattr(namespace, self.dest, actual) 491 492 493def print_active_checkers(checkers): 494 """ Print active checkers to stdout. """ 495 496 for name in sorted(name for name, (_, active) in checkers.items() 497 if active): 498 print(name) 499 500 501def print_checkers(checkers): 502 """ Print verbose checker help to stdout. """ 503 504 print('') 505 print('available checkers:') 506 print('') 507 for name in sorted(checkers.keys()): 508 description, active = checkers[name] 509 prefix = '+' if active else ' ' 510 if len(name) > 30: 511 print(' {0} {1}'.format(prefix, name)) 512 print(' ' * 35 + description) 513 else: 514 print(' {0} {1: <30} {2}'.format(prefix, name, description)) 515 print('') 516 print('NOTE: "+" indicates that an analysis is enabled by default.') 517 print('') 518