Subversion Repositories QNX 8.QNX8 LLVM/Clang compiler suite

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14 pmbaty 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
 
7
It uses argparse module to create the command line parser. (This library is
8
in the standard python library since 3.2 and backported to 2.7, but not
9
earlier.)
10
 
11
It also implements basic validation methods, related to the command.
12
Validations are mostly calling specific help methods, or mangling values.
13
"""
14
from __future__ import absolute_import, division, print_function
15
 
16
import os
17
import sys
18
import argparse
19
import logging
20
import tempfile
21
from libscanbuild import reconfigure_logging, CtuConfig
22
from 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
 
28
def 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
 
45
def 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
 
61
def 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
 
77
def 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
 
107
def 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
 
143
def 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
 
167
def 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
 
425
def 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
 
441
def 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
 
449
def 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
 
457
def 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
 
480
class 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
 
493
def 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
 
501
def 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('')