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