Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | # The LLVM Compiler Infrastructure |
| 3 | # |
| 4 | # This file is distributed under the University of Illinois Open Source |
| 5 | # License. See LICENSE.TXT for details. |
| 6 | """ This module implements the 'scan-build' command API. |
| 7 | |
| 8 | To run the static analyzer against a build is done in multiple steps: |
| 9 | |
| 10 | -- Intercept: capture the compilation command during the build, |
| 11 | -- Analyze: run the analyzer against the captured commands, |
| 12 | -- Report: create a cover report from the analyzer outputs. """ |
| 13 | |
| 14 | import sys |
| 15 | import re |
| 16 | import os |
| 17 | import os.path |
| 18 | import json |
| 19 | import argparse |
| 20 | import logging |
| 21 | import subprocess |
| 22 | import multiprocessing |
| 23 | from libscanbuild import initialize_logging, tempdir, command_entry_point |
| 24 | from libscanbuild.runner import run |
| 25 | from libscanbuild.intercept import capture |
| 26 | from libscanbuild.report import report_directory, document |
| 27 | from libscanbuild.clang import get_checkers |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 28 | from libscanbuild.compilation import split_command |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 29 | |
| 30 | __all__ = ['analyze_build_main', 'analyze_build_wrapper'] |
| 31 | |
| 32 | COMPILER_WRAPPER_CC = 'analyze-cc' |
| 33 | COMPILER_WRAPPER_CXX = 'analyze-c++' |
| 34 | |
| 35 | |
| 36 | @command_entry_point |
| 37 | def analyze_build_main(bin_dir, from_build_command): |
| 38 | """ Entry point for 'analyze-build' and 'scan-build'. """ |
| 39 | |
| 40 | parser = create_parser(from_build_command) |
| 41 | args = parser.parse_args() |
| 42 | validate(parser, args, from_build_command) |
| 43 | |
| 44 | # setup logging |
| 45 | initialize_logging(args.verbose) |
| 46 | logging.debug('Parsed arguments: %s', args) |
| 47 | |
| 48 | with report_directory(args.output, args.keep_empty) as target_dir: |
| 49 | if not from_build_command: |
| 50 | # run analyzer only and generate cover report |
| 51 | run_analyzer(args, target_dir) |
| 52 | number_of_bugs = document(args, target_dir, True) |
| 53 | return number_of_bugs if args.status_bugs else 0 |
| 54 | elif args.intercept_first: |
| 55 | # run build command and capture compiler executions |
| 56 | exit_code = capture(args, bin_dir) |
| 57 | # next step to run the analyzer against the captured commands |
| 58 | if need_analyzer(args.build): |
| 59 | run_analyzer(args, target_dir) |
| 60 | # cover report generation and bug counting |
| 61 | number_of_bugs = document(args, target_dir, True) |
| 62 | # remove the compilation database when it was not requested |
| 63 | if os.path.exists(args.cdb): |
| 64 | os.unlink(args.cdb) |
| 65 | # set exit status as it was requested |
| 66 | return number_of_bugs if args.status_bugs else exit_code |
| 67 | else: |
| 68 | return exit_code |
| 69 | else: |
| 70 | # run the build command with compiler wrappers which |
| 71 | # execute the analyzer too. (interposition) |
| 72 | environment = setup_environment(args, target_dir, bin_dir) |
| 73 | logging.debug('run build in environment: %s', environment) |
| 74 | exit_code = subprocess.call(args.build, env=environment) |
| 75 | logging.debug('build finished with exit code: %d', exit_code) |
| 76 | # cover report generation and bug counting |
| 77 | number_of_bugs = document(args, target_dir, False) |
| 78 | # set exit status as it was requested |
| 79 | return number_of_bugs if args.status_bugs else exit_code |
| 80 | |
| 81 | |
| 82 | def need_analyzer(args): |
| 83 | """ Check the intent of the build command. |
| 84 | |
| 85 | When static analyzer run against project configure step, it should be |
| 86 | silent and no need to run the analyzer or generate report. |
| 87 | |
| 88 | To run `scan-build` against the configure step might be neccessary, |
| 89 | when compiler wrappers are used. That's the moment when build setup |
| 90 | check the compiler and capture the location for the build process. """ |
| 91 | |
| 92 | return len(args) and not re.search('configure|autogen', args[0]) |
| 93 | |
| 94 | |
| 95 | def run_analyzer(args, output_dir): |
| 96 | """ Runs the analyzer against the given compilation database. """ |
| 97 | |
| 98 | def exclude(filename): |
| 99 | """ Return true when any excluded directory prefix the filename. """ |
| 100 | return any(re.match(r'^' + directory, filename) |
| 101 | for directory in args.excludes) |
| 102 | |
| 103 | consts = { |
| 104 | 'clang': args.clang, |
| 105 | 'output_dir': output_dir, |
| 106 | 'output_format': args.output_format, |
| 107 | 'output_failures': args.output_failures, |
Yury Gribov | a6560eb | 2016-02-18 11:08:46 +0000 | [diff] [blame] | 108 | 'direct_args': analyzer_params(args), |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 109 | 'force_debug': args.force_debug |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | logging.debug('run analyzer against compilation database') |
| 113 | with open(args.cdb, 'r') as handle: |
| 114 | generator = (dict(cmd, **consts) |
| 115 | for cmd in json.load(handle) if not exclude(cmd['file'])) |
| 116 | # when verbose output requested execute sequentially |
| 117 | pool = multiprocessing.Pool(1 if args.verbose > 2 else None) |
| 118 | for current in pool.imap_unordered(run, generator): |
| 119 | if current is not None: |
| 120 | # display error message from the static analyzer |
| 121 | for line in current['error_output']: |
| 122 | logging.info(line.rstrip()) |
| 123 | pool.close() |
| 124 | pool.join() |
| 125 | |
| 126 | |
| 127 | def setup_environment(args, destination, bin_dir): |
| 128 | """ Set up environment for build command to interpose compiler wrapper. """ |
| 129 | |
| 130 | environment = dict(os.environ) |
| 131 | environment.update({ |
| 132 | 'CC': os.path.join(bin_dir, COMPILER_WRAPPER_CC), |
| 133 | 'CXX': os.path.join(bin_dir, COMPILER_WRAPPER_CXX), |
| 134 | 'ANALYZE_BUILD_CC': args.cc, |
| 135 | 'ANALYZE_BUILD_CXX': args.cxx, |
| 136 | 'ANALYZE_BUILD_CLANG': args.clang if need_analyzer(args.build) else '', |
| 137 | 'ANALYZE_BUILD_VERBOSE': 'DEBUG' if args.verbose > 2 else 'WARNING', |
| 138 | 'ANALYZE_BUILD_REPORT_DIR': destination, |
| 139 | 'ANALYZE_BUILD_REPORT_FORMAT': args.output_format, |
| 140 | 'ANALYZE_BUILD_REPORT_FAILURES': 'yes' if args.output_failures else '', |
Yury Gribov | a6560eb | 2016-02-18 11:08:46 +0000 | [diff] [blame] | 141 | 'ANALYZE_BUILD_PARAMETERS': ' '.join(analyzer_params(args)), |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 142 | 'ANALYZE_BUILD_FORCE_DEBUG': 'yes' if args.force_debug else '' |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 143 | }) |
| 144 | return environment |
| 145 | |
| 146 | |
| 147 | def analyze_build_wrapper(cplusplus): |
| 148 | """ Entry point for `analyze-cc` and `analyze-c++` compiler wrappers. """ |
| 149 | |
| 150 | # initialize wrapper logging |
| 151 | logging.basicConfig(format='analyze: %(levelname)s: %(message)s', |
| 152 | level=os.getenv('ANALYZE_BUILD_VERBOSE', 'INFO')) |
| 153 | # execute with real compiler |
| 154 | compiler = os.getenv('ANALYZE_BUILD_CXX', 'c++') if cplusplus \ |
| 155 | else os.getenv('ANALYZE_BUILD_CC', 'cc') |
| 156 | compilation = [compiler] + sys.argv[1:] |
| 157 | logging.info('execute compiler: %s', compilation) |
| 158 | result = subprocess.call(compilation) |
| 159 | # exit when it fails, ... |
| 160 | if result or not os.getenv('ANALYZE_BUILD_CLANG'): |
| 161 | return result |
| 162 | # ... and run the analyzer if all went well. |
| 163 | try: |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 164 | # check is it a compilation |
| 165 | compilation = split_command(sys.argv) |
| 166 | if compilation is None: |
| 167 | return result |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 168 | # collect the needed parameters from environment, crash when missing |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 169 | parameters = { |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 170 | 'clang': os.getenv('ANALYZE_BUILD_CLANG'), |
| 171 | 'output_dir': os.getenv('ANALYZE_BUILD_REPORT_DIR'), |
| 172 | 'output_format': os.getenv('ANALYZE_BUILD_REPORT_FORMAT'), |
| 173 | 'output_failures': os.getenv('ANALYZE_BUILD_REPORT_FAILURES'), |
| 174 | 'direct_args': os.getenv('ANALYZE_BUILD_PARAMETERS', |
| 175 | '').split(' '), |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 176 | 'force_debug': os.getenv('ANALYZE_BUILD_FORCE_DEBUG'), |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 177 | 'directory': os.getcwd(), |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 178 | 'command': [sys.argv[0], '-c'] + compilation.flags |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 179 | } |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 180 | # call static analyzer against the compilation |
| 181 | for source in compilation.files: |
| 182 | parameters.update({'file': source}) |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 183 | logging.debug('analyzer parameters %s', parameters) |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 184 | current = run(parameters) |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 185 | # display error message from the static analyzer |
| 186 | if current is not None: |
| 187 | for line in current['error_output']: |
| 188 | logging.info(line.rstrip()) |
| 189 | except Exception: |
| 190 | logging.exception("run analyzer inside compiler wrapper failed.") |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 191 | return result |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 192 | |
| 193 | |
| 194 | def analyzer_params(args): |
| 195 | """ A group of command line arguments can mapped to command |
| 196 | line arguments of the analyzer. This method generates those. """ |
| 197 | |
| 198 | def prefix_with(constant, pieces): |
| 199 | """ From a sequence create another sequence where every second element |
| 200 | is from the original sequence and the odd elements are the prefix. |
| 201 | |
| 202 | eg.: prefix_with(0, [1,2,3]) creates [0, 1, 0, 2, 0, 3] """ |
| 203 | |
| 204 | return [elem for piece in pieces for elem in [constant, piece]] |
| 205 | |
| 206 | result = [] |
| 207 | |
| 208 | if args.store_model: |
| 209 | result.append('-analyzer-store={0}'.format(args.store_model)) |
| 210 | if args.constraints_model: |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 211 | result.append('-analyzer-constraints={0}'.format( |
| 212 | args.constraints_model)) |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 213 | if args.internal_stats: |
| 214 | result.append('-analyzer-stats') |
| 215 | if args.analyze_headers: |
| 216 | result.append('-analyzer-opt-analyze-headers') |
| 217 | if args.stats: |
| 218 | result.append('-analyzer-checker=debug.Stats') |
| 219 | if args.maxloop: |
| 220 | result.extend(['-analyzer-max-loop', str(args.maxloop)]) |
| 221 | if args.output_format: |
| 222 | result.append('-analyzer-output={0}'.format(args.output_format)) |
| 223 | if args.analyzer_config: |
| 224 | result.append(args.analyzer_config) |
| 225 | if args.verbose >= 4: |
| 226 | result.append('-analyzer-display-progress') |
| 227 | if args.plugins: |
| 228 | result.extend(prefix_with('-load', args.plugins)) |
| 229 | if args.enable_checker: |
| 230 | checkers = ','.join(args.enable_checker) |
| 231 | result.extend(['-analyzer-checker', checkers]) |
| 232 | if args.disable_checker: |
| 233 | checkers = ','.join(args.disable_checker) |
| 234 | result.extend(['-analyzer-disable-checker', checkers]) |
| 235 | if os.getenv('UBIVIZ'): |
| 236 | result.append('-analyzer-viz-egraph-ubigraph') |
| 237 | |
| 238 | return prefix_with('-Xclang', result) |
| 239 | |
| 240 | |
| 241 | def print_active_checkers(checkers): |
| 242 | """ Print active checkers to stdout. """ |
| 243 | |
| 244 | for name in sorted(name for name, (_, active) in checkers.items() |
| 245 | if active): |
| 246 | print(name) |
| 247 | |
| 248 | |
| 249 | def print_checkers(checkers): |
| 250 | """ Print verbose checker help to stdout. """ |
| 251 | |
| 252 | print('') |
| 253 | print('available checkers:') |
| 254 | print('') |
| 255 | for name in sorted(checkers.keys()): |
| 256 | description, active = checkers[name] |
| 257 | prefix = '+' if active else ' ' |
| 258 | if len(name) > 30: |
| 259 | print(' {0} {1}'.format(prefix, name)) |
| 260 | print(' ' * 35 + description) |
| 261 | else: |
| 262 | print(' {0} {1: <30} {2}'.format(prefix, name, description)) |
| 263 | print('') |
| 264 | print('NOTE: "+" indicates that an analysis is enabled by default.') |
| 265 | print('') |
| 266 | |
| 267 | |
| 268 | def validate(parser, args, from_build_command): |
| 269 | """ Validation done by the parser itself, but semantic check still |
| 270 | needs to be done. This method is doing that. """ |
| 271 | |
Laszlo Nagy | 4f6a175 | 2016-09-24 00:20:59 +0000 | [diff] [blame^] | 272 | # Make plugins always a list. (It might be None when not specified.) |
| 273 | args.plugins = args.plugins if args.plugins else [] |
| 274 | |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 275 | if args.help_checkers_verbose: |
| 276 | print_checkers(get_checkers(args.clang, args.plugins)) |
| 277 | parser.exit() |
| 278 | elif args.help_checkers: |
| 279 | print_active_checkers(get_checkers(args.clang, args.plugins)) |
| 280 | parser.exit() |
| 281 | |
| 282 | if from_build_command and not args.build: |
| 283 | parser.error('missing build command') |
| 284 | |
| 285 | |
| 286 | def create_parser(from_build_command): |
| 287 | """ Command line argument parser factory method. """ |
| 288 | |
| 289 | parser = argparse.ArgumentParser( |
| 290 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| 291 | |
| 292 | parser.add_argument( |
| 293 | '--verbose', '-v', |
| 294 | action='count', |
| 295 | default=0, |
| 296 | help="""Enable verbose output from '%(prog)s'. A second and third |
| 297 | flag increases verbosity.""") |
| 298 | parser.add_argument( |
| 299 | '--override-compiler', |
| 300 | action='store_true', |
| 301 | help="""Always resort to the compiler wrapper even when better |
| 302 | interposition methods are available.""") |
| 303 | parser.add_argument( |
| 304 | '--intercept-first', |
| 305 | action='store_true', |
| 306 | help="""Run the build commands only, build a compilation database, |
| 307 | then run the static analyzer afterwards. |
| 308 | Generally speaking it has better coverage on build commands. |
| 309 | With '--override-compiler' it use compiler wrapper, but does |
| 310 | not run the analyzer till the build is finished. """) |
| 311 | parser.add_argument( |
| 312 | '--cdb', |
| 313 | metavar='<file>', |
| 314 | default="compile_commands.json", |
| 315 | help="""The JSON compilation database.""") |
| 316 | |
| 317 | parser.add_argument( |
| 318 | '--output', '-o', |
| 319 | metavar='<path>', |
| 320 | default=tempdir(), |
| 321 | help="""Specifies the output directory for analyzer reports. |
| 322 | Subdirectory will be created if default directory is targeted. |
| 323 | """) |
| 324 | parser.add_argument( |
| 325 | '--status-bugs', |
| 326 | action='store_true', |
| 327 | help="""By default, the exit status of '%(prog)s' is the same as the |
| 328 | executed build command. Specifying this option causes the exit |
| 329 | status of '%(prog)s' to be non zero if it found potential bugs |
| 330 | and zero otherwise.""") |
| 331 | parser.add_argument( |
| 332 | '--html-title', |
| 333 | metavar='<title>', |
| 334 | help="""Specify the title used on generated HTML pages. |
| 335 | If not specified, a default title will be used.""") |
| 336 | parser.add_argument( |
| 337 | '--analyze-headers', |
| 338 | action='store_true', |
| 339 | help="""Also analyze functions in #included files. By default, such |
| 340 | functions are skipped unless they are called by functions |
| 341 | within the main source file.""") |
| 342 | format_group = parser.add_mutually_exclusive_group() |
| 343 | format_group.add_argument( |
| 344 | '--plist', '-plist', |
| 345 | dest='output_format', |
| 346 | const='plist', |
| 347 | default='html', |
| 348 | action='store_const', |
| 349 | help="""This option outputs the results as a set of .plist files.""") |
| 350 | format_group.add_argument( |
| 351 | '--plist-html', '-plist-html', |
| 352 | dest='output_format', |
| 353 | const='plist-html', |
| 354 | default='html', |
| 355 | action='store_const', |
| 356 | help="""This option outputs the results as a set of .html and .plist |
| 357 | files.""") |
| 358 | # TODO: implement '-view ' |
| 359 | |
| 360 | advanced = parser.add_argument_group('advanced options') |
| 361 | advanced.add_argument( |
| 362 | '--keep-empty', |
| 363 | action='store_true', |
| 364 | help="""Don't remove the build results directory even if no issues |
| 365 | were reported.""") |
| 366 | advanced.add_argument( |
| 367 | '--no-failure-reports', '-no-failure-reports', |
| 368 | dest='output_failures', |
| 369 | action='store_false', |
| 370 | help="""Do not create a 'failures' subdirectory that includes analyzer |
| 371 | crash reports and preprocessed source files.""") |
| 372 | advanced.add_argument( |
| 373 | '--stats', '-stats', |
| 374 | action='store_true', |
| 375 | help="""Generates visitation statistics for the project being analyzed. |
| 376 | """) |
| 377 | advanced.add_argument( |
| 378 | '--internal-stats', |
| 379 | action='store_true', |
| 380 | help="""Generate internal analyzer statistics.""") |
| 381 | advanced.add_argument( |
| 382 | '--maxloop', '-maxloop', |
| 383 | metavar='<loop count>', |
| 384 | type=int, |
| 385 | help="""Specifiy the number of times a block can be visited before |
| 386 | giving up. Increase for more comprehensive coverage at a cost |
| 387 | of speed.""") |
| 388 | advanced.add_argument( |
| 389 | '--store', '-store', |
| 390 | metavar='<model>', |
| 391 | dest='store_model', |
| 392 | choices=['region', 'basic'], |
| 393 | help="""Specify the store model used by the analyzer. |
| 394 | 'region' specifies a field- sensitive store model. |
| 395 | 'basic' which is far less precise but can more quickly |
| 396 | analyze code. 'basic' was the default store model for |
| 397 | checker-0.221 and earlier.""") |
| 398 | advanced.add_argument( |
| 399 | '--constraints', '-constraints', |
| 400 | metavar='<model>', |
| 401 | dest='constraints_model', |
| 402 | choices=['range', 'basic'], |
| 403 | help="""Specify the contraint engine used by the analyzer. Specifying |
| 404 | 'basic' uses a simpler, less powerful constraint model used by |
| 405 | checker-0.160 and earlier.""") |
| 406 | advanced.add_argument( |
| 407 | '--use-analyzer', |
| 408 | metavar='<path>', |
| 409 | dest='clang', |
| 410 | default='clang', |
| 411 | help="""'%(prog)s' uses the 'clang' executable relative to itself for |
| 412 | static analysis. One can override this behavior with this |
| 413 | option by using the 'clang' packaged with Xcode (on OS X) or |
| 414 | from the PATH.""") |
| 415 | advanced.add_argument( |
| 416 | '--use-cc', |
| 417 | metavar='<path>', |
| 418 | dest='cc', |
| 419 | default='cc', |
| 420 | help="""When '%(prog)s' analyzes a project by interposing a "fake |
| 421 | compiler", which executes a real compiler for compilation and |
| 422 | do other tasks (to run the static analyzer or just record the |
| 423 | compiler invocation). Because of this interposing, '%(prog)s' |
| 424 | does not know what compiler your project normally uses. |
| 425 | Instead, it simply overrides the CC environment variable, and |
| 426 | guesses your default compiler. |
| 427 | |
| 428 | If you need '%(prog)s' to use a specific compiler for |
| 429 | *compilation* then you can use this option to specify a path |
| 430 | to that compiler.""") |
| 431 | advanced.add_argument( |
| 432 | '--use-c++', |
| 433 | metavar='<path>', |
| 434 | dest='cxx', |
| 435 | default='c++', |
| 436 | help="""This is the same as "--use-cc" but for C++ code.""") |
| 437 | advanced.add_argument( |
| 438 | '--analyzer-config', '-analyzer-config', |
| 439 | metavar='<options>', |
| 440 | help="""Provide options to pass through to the analyzer's |
| 441 | -analyzer-config flag. Several options are separated with |
| 442 | comma: 'key1=val1,key2=val2' |
| 443 | |
| 444 | Available options: |
| 445 | stable-report-filename=true or false (default) |
| 446 | |
| 447 | Switch the page naming to: |
| 448 | report-<filename>-<function/method name>-<id>.html |
| 449 | instead of report-XXXXXX.html""") |
| 450 | advanced.add_argument( |
| 451 | '--exclude', |
| 452 | metavar='<directory>', |
| 453 | dest='excludes', |
| 454 | action='append', |
| 455 | default=[], |
| 456 | help="""Do not run static analyzer against files found in this |
| 457 | directory. (You can specify this option multiple times.) |
| 458 | Could be usefull when project contains 3rd party libraries. |
| 459 | The directory path shall be absolute path as file names in |
| 460 | the compilation database.""") |
Yury Gribov | a6560eb | 2016-02-18 11:08:46 +0000 | [diff] [blame] | 461 | advanced.add_argument( |
| 462 | '--force-analyze-debug-code', |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 463 | dest='force_debug', |
Yury Gribov | a6560eb | 2016-02-18 11:08:46 +0000 | [diff] [blame] | 464 | action='store_true', |
| 465 | help="""Tells analyzer to enable assertions in code even if they were |
Laszlo Nagy | 8bd63e5 | 2016-04-19 12:03:03 +0000 | [diff] [blame] | 466 | disabled during compilation, enabling more precise results.""") |
Laszlo Nagy | bc68758 | 2016-01-12 22:38:41 +0000 | [diff] [blame] | 467 | |
| 468 | plugins = parser.add_argument_group('checker options') |
| 469 | plugins.add_argument( |
| 470 | '--load-plugin', '-load-plugin', |
| 471 | metavar='<plugin library>', |
| 472 | dest='plugins', |
| 473 | action='append', |
| 474 | help="""Loading external checkers using the clang plugin interface.""") |
| 475 | plugins.add_argument( |
| 476 | '--enable-checker', '-enable-checker', |
| 477 | metavar='<checker name>', |
| 478 | action=AppendCommaSeparated, |
| 479 | help="""Enable specific checker.""") |
| 480 | plugins.add_argument( |
| 481 | '--disable-checker', '-disable-checker', |
| 482 | metavar='<checker name>', |
| 483 | action=AppendCommaSeparated, |
| 484 | help="""Disable specific checker.""") |
| 485 | plugins.add_argument( |
| 486 | '--help-checkers', |
| 487 | action='store_true', |
| 488 | help="""A default group of checkers is run unless explicitly disabled. |
| 489 | Exactly which checkers constitute the default group is a |
| 490 | function of the operating system in use. These can be printed |
| 491 | with this flag.""") |
| 492 | plugins.add_argument( |
| 493 | '--help-checkers-verbose', |
| 494 | action='store_true', |
| 495 | help="""Print all available checkers and mark the enabled ones.""") |
| 496 | |
| 497 | if from_build_command: |
| 498 | parser.add_argument( |
| 499 | dest='build', |
| 500 | nargs=argparse.REMAINDER, |
| 501 | help="""Command to run.""") |
| 502 | |
| 503 | return parser |
| 504 | |
| 505 | |
| 506 | class AppendCommaSeparated(argparse.Action): |
| 507 | """ argparse Action class to support multiple comma separated lists. """ |
| 508 | |
| 509 | def __call__(self, __parser, namespace, values, __option_string): |
| 510 | # getattr(obj, attr, default) does not really returns default but none |
| 511 | if getattr(namespace, self.dest, None) is None: |
| 512 | setattr(namespace, self.dest, []) |
| 513 | # once it's fixed we can use as expected |
| 514 | actual = getattr(namespace, self.dest) |
| 515 | actual.extend(values.split(',')) |
| 516 | setattr(namespace, self.dest, actual) |