xref: /llvm-project/clang/tools/scan-build-py/lib/libscanbuild/analyze.py (revision dd3c26a045c081620375a878159f536758baba6e)
1d9cf8291SDaniel Hwang# -*- coding: utf-8 -*-
2d9cf8291SDaniel Hwang# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3d9cf8291SDaniel Hwang# See https://llvm.org/LICENSE.txt for license information.
4d9cf8291SDaniel Hwang# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5d9cf8291SDaniel Hwang""" This module implements the 'scan-build' command API.
6d9cf8291SDaniel Hwang
7d9cf8291SDaniel HwangTo run the static analyzer against a build is done in multiple steps:
8d9cf8291SDaniel Hwang
9d9cf8291SDaniel Hwang -- Intercept: capture the compilation command during the build,
10d9cf8291SDaniel Hwang -- Analyze:   run the analyzer against the captured commands,
11d9cf8291SDaniel Hwang -- Report:    create a cover report from the analyzer outputs.  """
12d9cf8291SDaniel Hwang
13d9cf8291SDaniel Hwangimport re
14d9cf8291SDaniel Hwangimport os
15d9cf8291SDaniel Hwangimport os.path
16d9cf8291SDaniel Hwangimport json
17d9cf8291SDaniel Hwangimport logging
18d9cf8291SDaniel Hwangimport multiprocessing
19d9cf8291SDaniel Hwangimport tempfile
20d9cf8291SDaniel Hwangimport functools
21d9cf8291SDaniel Hwangimport subprocess
22d9cf8291SDaniel Hwangimport contextlib
23d9cf8291SDaniel Hwangimport datetime
24d9cf8291SDaniel Hwangimport shutil
25d9cf8291SDaniel Hwangimport glob
26d9cf8291SDaniel Hwangfrom collections import defaultdict
27d9cf8291SDaniel Hwang
28*dd3c26a0STobias Hietafrom libscanbuild import (
29*dd3c26a0STobias Hieta    command_entry_point,
30*dd3c26a0STobias Hieta    compiler_wrapper,
31*dd3c26a0STobias Hieta    wrapper_environment,
32*dd3c26a0STobias Hieta    run_build,
33*dd3c26a0STobias Hieta    run_command,
34*dd3c26a0STobias Hieta    CtuConfig,
35*dd3c26a0STobias Hieta)
36*dd3c26a0STobias Hietafrom libscanbuild.arguments import (
37*dd3c26a0STobias Hieta    parse_args_for_scan_build,
38*dd3c26a0STobias Hieta    parse_args_for_analyze_build,
39*dd3c26a0STobias Hieta)
40d9cf8291SDaniel Hwangfrom libscanbuild.intercept import capture
41d9cf8291SDaniel Hwangfrom libscanbuild.report import document
42*dd3c26a0STobias Hietafrom libscanbuild.compilation import split_command, classify_source, compiler_language
43*dd3c26a0STobias Hietafrom libscanbuild.clang import (
44*dd3c26a0STobias Hieta    get_version,
45*dd3c26a0STobias Hieta    get_arguments,
46*dd3c26a0STobias Hieta    get_triple_arch,
47*dd3c26a0STobias Hieta    ClangErrorException,
48*dd3c26a0STobias Hieta)
49d9cf8291SDaniel Hwangfrom libscanbuild.shell import decode
50d9cf8291SDaniel Hwang
51*dd3c26a0STobias Hieta__all__ = ["scan_build", "analyze_build", "analyze_compiler_wrapper"]
52d9cf8291SDaniel Hwang
53*dd3c26a0STobias Hietascanbuild_dir = os.path.dirname(os.path.realpath(__import__("sys").argv[0]))
54c84755a0Sserge-sans-paille
55*dd3c26a0STobias HietaCOMPILER_WRAPPER_CC = os.path.join(scanbuild_dir, "..", "libexec", "analyze-cc")
56*dd3c26a0STobias HietaCOMPILER_WRAPPER_CXX = os.path.join(scanbuild_dir, "..", "libexec", "analyze-c++")
57d9cf8291SDaniel Hwang
58*dd3c26a0STobias HietaCTU_EXTDEF_MAP_FILENAME = "externalDefMap.txt"
59*dd3c26a0STobias HietaCTU_TEMP_DEFMAP_FOLDER = "tmpExternalDefMaps"
60d9cf8291SDaniel Hwang
61d9cf8291SDaniel Hwang
62d9cf8291SDaniel Hwang@command_entry_point
63d9cf8291SDaniel Hwangdef scan_build():
64d9cf8291SDaniel Hwang    """Entry point for scan-build command."""
65d9cf8291SDaniel Hwang
66d9cf8291SDaniel Hwang    args = parse_args_for_scan_build()
67d9cf8291SDaniel Hwang    # will re-assign the report directory as new output
68d9cf8291SDaniel Hwang    with report_directory(
69*dd3c26a0STobias Hieta        args.output, args.keep_empty, args.output_format
70*dd3c26a0STobias Hieta    ) as args.output:
71d9cf8291SDaniel Hwang        # Run against a build command. there are cases, when analyzer run
72d9cf8291SDaniel Hwang        # is not required. But we need to set up everything for the
73d9cf8291SDaniel Hwang        # wrappers, because 'configure' needs to capture the CC/CXX values
74d9cf8291SDaniel Hwang        # for the Makefile.
75d9cf8291SDaniel Hwang        if args.intercept_first:
76d9cf8291SDaniel Hwang            # Run build command with intercept module.
77d9cf8291SDaniel Hwang            exit_code = capture(args)
78d9cf8291SDaniel Hwang            # Run the analyzer against the captured commands.
79d9cf8291SDaniel Hwang            if need_analyzer(args.build):
80d9cf8291SDaniel Hwang                govern_analyzer_runs(args)
81d9cf8291SDaniel Hwang        else:
82d9cf8291SDaniel Hwang            # Run build command and analyzer with compiler wrappers.
83d9cf8291SDaniel Hwang            environment = setup_environment(args)
84d9cf8291SDaniel Hwang            exit_code = run_build(args.build, env=environment)
85d9cf8291SDaniel Hwang        # Cover report generation and bug counting.
86d9cf8291SDaniel Hwang        number_of_bugs = document(args)
87d9cf8291SDaniel Hwang        # Set exit status as it was requested.
88d9cf8291SDaniel Hwang        return number_of_bugs if args.status_bugs else exit_code
89d9cf8291SDaniel Hwang
90d9cf8291SDaniel Hwang
91d9cf8291SDaniel Hwang@command_entry_point
92d9cf8291SDaniel Hwangdef analyze_build():
93d9cf8291SDaniel Hwang    """Entry point for analyze-build command."""
94d9cf8291SDaniel Hwang
95d9cf8291SDaniel Hwang    args = parse_args_for_analyze_build()
96d9cf8291SDaniel Hwang    # will re-assign the report directory as new output
97*dd3c26a0STobias Hieta    with report_directory(
98*dd3c26a0STobias Hieta        args.output, args.keep_empty, args.output_format
99*dd3c26a0STobias Hieta    ) as args.output:
100d9cf8291SDaniel Hwang        # Run the analyzer against a compilation db.
101d9cf8291SDaniel Hwang        govern_analyzer_runs(args)
102d9cf8291SDaniel Hwang        # Cover report generation and bug counting.
103d9cf8291SDaniel Hwang        number_of_bugs = document(args)
104d9cf8291SDaniel Hwang        # Set exit status as it was requested.
105d9cf8291SDaniel Hwang        return number_of_bugs if args.status_bugs else 0
106d9cf8291SDaniel Hwang
107d9cf8291SDaniel Hwang
108d9cf8291SDaniel Hwangdef need_analyzer(args):
109d9cf8291SDaniel Hwang    """Check the intent of the build command.
110d9cf8291SDaniel Hwang
111d9cf8291SDaniel Hwang    When static analyzer run against project configure step, it should be
112d9cf8291SDaniel Hwang    silent and no need to run the analyzer or generate report.
113d9cf8291SDaniel Hwang
114d9cf8291SDaniel Hwang    To run `scan-build` against the configure step might be necessary,
115d9cf8291SDaniel Hwang    when compiler wrappers are used. That's the moment when build setup
116d9cf8291SDaniel Hwang    check the compiler and capture the location for the build process."""
117d9cf8291SDaniel Hwang
118*dd3c26a0STobias Hieta    return len(args) and not re.search(r"configure|autogen", args[0])
119d9cf8291SDaniel Hwang
120d9cf8291SDaniel Hwang
121d9cf8291SDaniel Hwangdef prefix_with(constant, pieces):
122d9cf8291SDaniel Hwang    """From a sequence create another sequence where every second element
123d9cf8291SDaniel Hwang    is from the original sequence and the odd elements are the prefix.
124d9cf8291SDaniel Hwang
125d9cf8291SDaniel Hwang    eg.: prefix_with(0, [1,2,3]) creates [0, 1, 0, 2, 0, 3]"""
126d9cf8291SDaniel Hwang
127d9cf8291SDaniel Hwang    return [elem for piece in pieces for elem in [constant, piece]]
128d9cf8291SDaniel Hwang
129d9cf8291SDaniel Hwang
130d9cf8291SDaniel Hwangdef get_ctu_config_from_args(args):
131d9cf8291SDaniel Hwang    """CTU configuration is created from the chosen phases and dir."""
132d9cf8291SDaniel Hwang
133d9cf8291SDaniel Hwang    return (
134*dd3c26a0STobias Hieta        CtuConfig(
135*dd3c26a0STobias Hieta            collect=args.ctu_phases.collect,
136d9cf8291SDaniel Hwang            analyze=args.ctu_phases.analyze,
137d9cf8291SDaniel Hwang            dir=args.ctu_dir,
138*dd3c26a0STobias Hieta            extdef_map_cmd=args.extdef_map_cmd,
139*dd3c26a0STobias Hieta        )
140*dd3c26a0STobias Hieta        if hasattr(args, "ctu_phases") and hasattr(args.ctu_phases, "dir")
141*dd3c26a0STobias Hieta        else CtuConfig(collect=False, analyze=False, dir="", extdef_map_cmd="")
142*dd3c26a0STobias Hieta    )
143d9cf8291SDaniel Hwang
144d9cf8291SDaniel Hwang
145d9cf8291SDaniel Hwangdef get_ctu_config_from_json(ctu_conf_json):
146d9cf8291SDaniel Hwang    """CTU configuration is created from the chosen phases and dir."""
147d9cf8291SDaniel Hwang
148d9cf8291SDaniel Hwang    ctu_config = json.loads(ctu_conf_json)
149d9cf8291SDaniel Hwang    # Recover namedtuple from json when coming from analyze-cc or analyze-c++
150*dd3c26a0STobias Hieta    return CtuConfig(
151*dd3c26a0STobias Hieta        collect=ctu_config[0],
152d9cf8291SDaniel Hwang        analyze=ctu_config[1],
153d9cf8291SDaniel Hwang        dir=ctu_config[2],
154*dd3c26a0STobias Hieta        extdef_map_cmd=ctu_config[3],
155*dd3c26a0STobias Hieta    )
156d9cf8291SDaniel Hwang
157d9cf8291SDaniel Hwang
158d9cf8291SDaniel Hwangdef create_global_ctu_extdef_map(extdef_map_lines):
159d9cf8291SDaniel Hwang    """Takes iterator of individual external definition maps and creates a
160d9cf8291SDaniel Hwang    global map keeping only unique names. We leave conflicting names out of
161d9cf8291SDaniel Hwang    CTU.
162d9cf8291SDaniel Hwang
163d9cf8291SDaniel Hwang    :param extdef_map_lines: Contains the id of a definition (mangled name) and
164d9cf8291SDaniel Hwang    the originating source (the corresponding AST file) name.
165d9cf8291SDaniel Hwang    :type extdef_map_lines: Iterator of str.
166d9cf8291SDaniel Hwang    :returns: Mangled name - AST file pairs.
167d9cf8291SDaniel Hwang    :rtype: List of (str, str) tuples.
168d9cf8291SDaniel Hwang    """
169d9cf8291SDaniel Hwang
170d9cf8291SDaniel Hwang    mangled_to_asts = defaultdict(set)
171d9cf8291SDaniel Hwang
172d9cf8291SDaniel Hwang    for line in extdef_map_lines:
173*dd3c26a0STobias Hieta        mangled_name, ast_file = line.strip().split(" ", 1)
174d9cf8291SDaniel Hwang        mangled_to_asts[mangled_name].add(ast_file)
175d9cf8291SDaniel Hwang
176d9cf8291SDaniel Hwang    mangled_ast_pairs = []
177d9cf8291SDaniel Hwang
178d9cf8291SDaniel Hwang    for mangled_name, ast_files in mangled_to_asts.items():
179d9cf8291SDaniel Hwang        if len(ast_files) == 1:
180d9cf8291SDaniel Hwang            mangled_ast_pairs.append((mangled_name, next(iter(ast_files))))
181d9cf8291SDaniel Hwang
182d9cf8291SDaniel Hwang    return mangled_ast_pairs
183d9cf8291SDaniel Hwang
184d9cf8291SDaniel Hwang
185d9cf8291SDaniel Hwangdef merge_ctu_extdef_maps(ctudir):
186d9cf8291SDaniel Hwang    """Merge individual external definition maps into a global one.
187d9cf8291SDaniel Hwang
188d9cf8291SDaniel Hwang    As the collect phase runs parallel on multiple threads, all compilation
189d9cf8291SDaniel Hwang    units are separately mapped into a temporary file in CTU_TEMP_DEFMAP_FOLDER.
190d9cf8291SDaniel Hwang    These definition maps contain the mangled names and the source
191d9cf8291SDaniel Hwang    (AST generated from the source) which had their definition.
192d9cf8291SDaniel Hwang    These files should be merged at the end into a global map file:
193d9cf8291SDaniel Hwang    CTU_EXTDEF_MAP_FILENAME."""
194d9cf8291SDaniel Hwang
195d9cf8291SDaniel Hwang    def generate_extdef_map_lines(extdefmap_dir):
196d9cf8291SDaniel Hwang        """Iterate over all lines of input files in a determined order."""
197d9cf8291SDaniel Hwang
198*dd3c26a0STobias Hieta        files = glob.glob(os.path.join(extdefmap_dir, "*"))
199d9cf8291SDaniel Hwang        files.sort()
200d9cf8291SDaniel Hwang        for filename in files:
201*dd3c26a0STobias Hieta            with open(filename, "r") as in_file:
202d9cf8291SDaniel Hwang                for line in in_file:
203d9cf8291SDaniel Hwang                    yield line
204d9cf8291SDaniel Hwang
205d9cf8291SDaniel Hwang    def write_global_map(arch, mangled_ast_pairs):
206d9cf8291SDaniel Hwang        """Write (mangled name, ast file) pairs into final file."""
207d9cf8291SDaniel Hwang
208*dd3c26a0STobias Hieta        extern_defs_map_file = os.path.join(ctudir, arch, CTU_EXTDEF_MAP_FILENAME)
209*dd3c26a0STobias Hieta        with open(extern_defs_map_file, "w") as out_file:
210d9cf8291SDaniel Hwang            for mangled_name, ast_file in mangled_ast_pairs:
211*dd3c26a0STobias Hieta                out_file.write("%s %s\n" % (mangled_name, ast_file))
212d9cf8291SDaniel Hwang
213*dd3c26a0STobias Hieta    triple_arches = glob.glob(os.path.join(ctudir, "*"))
214d9cf8291SDaniel Hwang    for triple_path in triple_arches:
215d9cf8291SDaniel Hwang        if os.path.isdir(triple_path):
216d9cf8291SDaniel Hwang            triple_arch = os.path.basename(triple_path)
217*dd3c26a0STobias Hieta            extdefmap_dir = os.path.join(ctudir, triple_arch, CTU_TEMP_DEFMAP_FOLDER)
218d9cf8291SDaniel Hwang
219d9cf8291SDaniel Hwang            extdef_map_lines = generate_extdef_map_lines(extdefmap_dir)
220d9cf8291SDaniel Hwang            mangled_ast_pairs = create_global_ctu_extdef_map(extdef_map_lines)
221d9cf8291SDaniel Hwang            write_global_map(triple_arch, mangled_ast_pairs)
222d9cf8291SDaniel Hwang
223d9cf8291SDaniel Hwang            # Remove all temporary files
224d9cf8291SDaniel Hwang            shutil.rmtree(extdefmap_dir, ignore_errors=True)
225d9cf8291SDaniel Hwang
226d9cf8291SDaniel Hwang
227d9cf8291SDaniel Hwangdef run_analyzer_parallel(args):
228d9cf8291SDaniel Hwang    """Runs the analyzer against the given compilation database."""
229d9cf8291SDaniel Hwang
230d9cf8291SDaniel Hwang    def exclude(filename, directory):
231d9cf8291SDaniel Hwang        """Return true when any excluded directory prefix the filename."""
232d9cf8291SDaniel Hwang        if not os.path.isabs(filename):
233d9cf8291SDaniel Hwang            # filename is either absolute or relative to directory. Need to turn
234d9cf8291SDaniel Hwang            # it to absolute since 'args.excludes' are absolute paths.
235d9cf8291SDaniel Hwang            filename = os.path.normpath(os.path.join(directory, filename))
236*dd3c26a0STobias Hieta        return any(
237*dd3c26a0STobias Hieta            re.match(r"^" + exclude_directory, filename)
238*dd3c26a0STobias Hieta            for exclude_directory in args.excludes
239*dd3c26a0STobias Hieta        )
240d9cf8291SDaniel Hwang
241d9cf8291SDaniel Hwang    consts = {
242*dd3c26a0STobias Hieta        "clang": args.clang,
243*dd3c26a0STobias Hieta        "output_dir": args.output,
244*dd3c26a0STobias Hieta        "output_format": args.output_format,
245*dd3c26a0STobias Hieta        "output_failures": args.output_failures,
246*dd3c26a0STobias Hieta        "direct_args": analyzer_params(args),
247*dd3c26a0STobias Hieta        "force_debug": args.force_debug,
248*dd3c26a0STobias Hieta        "ctu": get_ctu_config_from_args(args),
249d9cf8291SDaniel Hwang    }
250d9cf8291SDaniel Hwang
251*dd3c26a0STobias Hieta    logging.debug("run analyzer against compilation database")
252*dd3c26a0STobias Hieta    with open(args.cdb, "r") as handle:
253*dd3c26a0STobias Hieta        generator = (
254*dd3c26a0STobias Hieta            dict(cmd, **consts)
255*dd3c26a0STobias Hieta            for cmd in json.load(handle)
256*dd3c26a0STobias Hieta            if not exclude(cmd["file"], cmd["directory"])
257*dd3c26a0STobias Hieta        )
258d9cf8291SDaniel Hwang        # when verbose output requested execute sequentially
259d9cf8291SDaniel Hwang        pool = multiprocessing.Pool(1 if args.verbose > 2 else None)
260d9cf8291SDaniel Hwang        for current in pool.imap_unordered(run, generator):
261d9cf8291SDaniel Hwang            if current is not None:
262d9cf8291SDaniel Hwang                # display error message from the static analyzer
263*dd3c26a0STobias Hieta                for line in current["error_output"]:
264d9cf8291SDaniel Hwang                    logging.info(line.rstrip())
265d9cf8291SDaniel Hwang        pool.close()
266d9cf8291SDaniel Hwang        pool.join()
267d9cf8291SDaniel Hwang
268d9cf8291SDaniel Hwang
269d9cf8291SDaniel Hwangdef govern_analyzer_runs(args):
270d9cf8291SDaniel Hwang    """Governs multiple runs in CTU mode or runs once in normal mode."""
271d9cf8291SDaniel Hwang
272d9cf8291SDaniel Hwang    ctu_config = get_ctu_config_from_args(args)
273d9cf8291SDaniel Hwang    # If we do a CTU collect (1st phase) we remove all previous collection
274d9cf8291SDaniel Hwang    # data first.
275d9cf8291SDaniel Hwang    if ctu_config.collect:
276d9cf8291SDaniel Hwang        shutil.rmtree(ctu_config.dir, ignore_errors=True)
277d9cf8291SDaniel Hwang
278d9cf8291SDaniel Hwang    # If the user asked for a collect (1st) and analyze (2nd) phase, we do an
279d9cf8291SDaniel Hwang    # all-in-one run where we deliberately remove collection data before and
280d9cf8291SDaniel Hwang    # also after the run. If the user asks only for a single phase data is
281d9cf8291SDaniel Hwang    # left so multiple analyze runs can use the same data gathered by a single
282d9cf8291SDaniel Hwang    # collection run.
283d9cf8291SDaniel Hwang    if ctu_config.collect and ctu_config.analyze:
284d9cf8291SDaniel Hwang        # CTU strings are coming from args.ctu_dir and extdef_map_cmd,
285d9cf8291SDaniel Hwang        # so we can leave it empty
286*dd3c26a0STobias Hieta        args.ctu_phases = CtuConfig(
287*dd3c26a0STobias Hieta            collect=True, analyze=False, dir="", extdef_map_cmd=""
288*dd3c26a0STobias Hieta        )
289d9cf8291SDaniel Hwang        run_analyzer_parallel(args)
290d9cf8291SDaniel Hwang        merge_ctu_extdef_maps(ctu_config.dir)
291*dd3c26a0STobias Hieta        args.ctu_phases = CtuConfig(
292*dd3c26a0STobias Hieta            collect=False, analyze=True, dir="", extdef_map_cmd=""
293*dd3c26a0STobias Hieta        )
294d9cf8291SDaniel Hwang        run_analyzer_parallel(args)
295d9cf8291SDaniel Hwang        shutil.rmtree(ctu_config.dir, ignore_errors=True)
296d9cf8291SDaniel Hwang    else:
297d9cf8291SDaniel Hwang        # Single runs (collect or analyze) are launched from here.
298d9cf8291SDaniel Hwang        run_analyzer_parallel(args)
299d9cf8291SDaniel Hwang        if ctu_config.collect:
300d9cf8291SDaniel Hwang            merge_ctu_extdef_maps(ctu_config.dir)
301d9cf8291SDaniel Hwang
302d9cf8291SDaniel Hwang
303d9cf8291SDaniel Hwangdef setup_environment(args):
304d9cf8291SDaniel Hwang    """Set up environment for build command to interpose compiler wrapper."""
305d9cf8291SDaniel Hwang
306d9cf8291SDaniel Hwang    environment = dict(os.environ)
307d9cf8291SDaniel Hwang    environment.update(wrapper_environment(args))
308*dd3c26a0STobias Hieta    environment.update(
309*dd3c26a0STobias Hieta        {
310*dd3c26a0STobias Hieta            "CC": COMPILER_WRAPPER_CC,
311*dd3c26a0STobias Hieta            "CXX": COMPILER_WRAPPER_CXX,
312*dd3c26a0STobias Hieta            "ANALYZE_BUILD_CLANG": args.clang if need_analyzer(args.build) else "",
313*dd3c26a0STobias Hieta            "ANALYZE_BUILD_REPORT_DIR": args.output,
314*dd3c26a0STobias Hieta            "ANALYZE_BUILD_REPORT_FORMAT": args.output_format,
315*dd3c26a0STobias Hieta            "ANALYZE_BUILD_REPORT_FAILURES": "yes" if args.output_failures else "",
316*dd3c26a0STobias Hieta            "ANALYZE_BUILD_PARAMETERS": " ".join(analyzer_params(args)),
317*dd3c26a0STobias Hieta            "ANALYZE_BUILD_FORCE_DEBUG": "yes" if args.force_debug else "",
318*dd3c26a0STobias Hieta            "ANALYZE_BUILD_CTU": json.dumps(get_ctu_config_from_args(args)),
319*dd3c26a0STobias Hieta        }
320*dd3c26a0STobias Hieta    )
321d9cf8291SDaniel Hwang    return environment
322d9cf8291SDaniel Hwang
323d9cf8291SDaniel Hwang
324d9cf8291SDaniel Hwang@command_entry_point
325d9cf8291SDaniel Hwangdef analyze_compiler_wrapper():
326d9cf8291SDaniel Hwang    """Entry point for `analyze-cc` and `analyze-c++` compiler wrappers."""
327d9cf8291SDaniel Hwang
328d9cf8291SDaniel Hwang    return compiler_wrapper(analyze_compiler_wrapper_impl)
329d9cf8291SDaniel Hwang
330d9cf8291SDaniel Hwang
331d9cf8291SDaniel Hwangdef analyze_compiler_wrapper_impl(result, execution):
332d9cf8291SDaniel Hwang    """Implements analyzer compiler wrapper functionality."""
333d9cf8291SDaniel Hwang
334d9cf8291SDaniel Hwang    # don't run analyzer when compilation fails. or when it's not requested.
335*dd3c26a0STobias Hieta    if result or not os.getenv("ANALYZE_BUILD_CLANG"):
336d9cf8291SDaniel Hwang        return
337d9cf8291SDaniel Hwang
338d9cf8291SDaniel Hwang    # check is it a compilation?
339d9cf8291SDaniel Hwang    compilation = split_command(execution.cmd)
340d9cf8291SDaniel Hwang    if compilation is None:
341d9cf8291SDaniel Hwang        return
342d9cf8291SDaniel Hwang    # collect the needed parameters from environment, crash when missing
343d9cf8291SDaniel Hwang    parameters = {
344*dd3c26a0STobias Hieta        "clang": os.getenv("ANALYZE_BUILD_CLANG"),
345*dd3c26a0STobias Hieta        "output_dir": os.getenv("ANALYZE_BUILD_REPORT_DIR"),
346*dd3c26a0STobias Hieta        "output_format": os.getenv("ANALYZE_BUILD_REPORT_FORMAT"),
347*dd3c26a0STobias Hieta        "output_failures": os.getenv("ANALYZE_BUILD_REPORT_FAILURES"),
348*dd3c26a0STobias Hieta        "direct_args": os.getenv("ANALYZE_BUILD_PARAMETERS", "").split(" "),
349*dd3c26a0STobias Hieta        "force_debug": os.getenv("ANALYZE_BUILD_FORCE_DEBUG"),
350*dd3c26a0STobias Hieta        "directory": execution.cwd,
351*dd3c26a0STobias Hieta        "command": [execution.cmd[0], "-c"] + compilation.flags,
352*dd3c26a0STobias Hieta        "ctu": get_ctu_config_from_json(os.getenv("ANALYZE_BUILD_CTU")),
353d9cf8291SDaniel Hwang    }
354d9cf8291SDaniel Hwang    # call static analyzer against the compilation
355d9cf8291SDaniel Hwang    for source in compilation.files:
356*dd3c26a0STobias Hieta        parameters.update({"file": source})
357*dd3c26a0STobias Hieta        logging.debug("analyzer parameters %s", parameters)
358d9cf8291SDaniel Hwang        current = run(parameters)
359d9cf8291SDaniel Hwang        # display error message from the static analyzer
360d9cf8291SDaniel Hwang        if current is not None:
361*dd3c26a0STobias Hieta            for line in current["error_output"]:
362d9cf8291SDaniel Hwang                logging.info(line.rstrip())
363d9cf8291SDaniel Hwang
364d9cf8291SDaniel Hwang
365d9cf8291SDaniel Hwang@contextlib.contextmanager
366d9cf8291SDaniel Hwangdef report_directory(hint, keep, output_format):
367d9cf8291SDaniel Hwang    """Responsible for the report directory.
368d9cf8291SDaniel Hwang
369d9cf8291SDaniel Hwang    hint -- could specify the parent directory of the output directory.
370d9cf8291SDaniel Hwang    keep -- a boolean value to keep or delete the empty report directory."""
371d9cf8291SDaniel Hwang
372*dd3c26a0STobias Hieta    stamp_format = "scan-build-%Y-%m-%d-%H-%M-%S-%f-"
373d9cf8291SDaniel Hwang    stamp = datetime.datetime.now().strftime(stamp_format)
374d9cf8291SDaniel Hwang    parent_dir = os.path.abspath(hint)
375d9cf8291SDaniel Hwang    if not os.path.exists(parent_dir):
376d9cf8291SDaniel Hwang        os.makedirs(parent_dir)
377d9cf8291SDaniel Hwang    name = tempfile.mkdtemp(prefix=stamp, dir=parent_dir)
378d9cf8291SDaniel Hwang
379*dd3c26a0STobias Hieta    logging.info("Report directory created: %s", name)
380d9cf8291SDaniel Hwang
381d9cf8291SDaniel Hwang    try:
382d9cf8291SDaniel Hwang        yield name
383d9cf8291SDaniel Hwang    finally:
384ff4abe75SAnders Waldenborg        args = (name,)
385d9cf8291SDaniel Hwang        if os.listdir(name):
386*dd3c26a0STobias Hieta            if output_format not in ["sarif", "sarif-html"]:  # FIXME:
387d9cf8291SDaniel Hwang                # 'scan-view' currently does not support sarif format.
388d9cf8291SDaniel Hwang                msg = "Run 'scan-view %s' to examine bug reports."
389*dd3c26a0STobias Hieta            elif output_format == "sarif-html":
390*dd3c26a0STobias Hieta                msg = (
391*dd3c26a0STobias Hieta                    "Run 'scan-view %s' to examine bug reports or see "
392d9cf8291SDaniel Hwang                    "merged sarif results at %s/results-merged.sarif."
393*dd3c26a0STobias Hieta                )
394ff4abe75SAnders Waldenborg                args = (name, name)
395d9cf8291SDaniel Hwang            else:
396d9cf8291SDaniel Hwang                msg = "View merged sarif results at %s/results-merged.sarif."
397d9cf8291SDaniel Hwang            keep = True
398d9cf8291SDaniel Hwang        else:
399d9cf8291SDaniel Hwang            if keep:
400d9cf8291SDaniel Hwang                msg = "Report directory '%s' contains no report, but kept."
401d9cf8291SDaniel Hwang            else:
402d9cf8291SDaniel Hwang                msg = "Removing directory '%s' because it contains no report."
403ff4abe75SAnders Waldenborg        logging.warning(msg, *args)
404d9cf8291SDaniel Hwang
405d9cf8291SDaniel Hwang        if not keep:
406d9cf8291SDaniel Hwang            os.rmdir(name)
407d9cf8291SDaniel Hwang
408d9cf8291SDaniel Hwang
409d9cf8291SDaniel Hwangdef analyzer_params(args):
410d9cf8291SDaniel Hwang    """A group of command line arguments can mapped to command
411d9cf8291SDaniel Hwang    line arguments of the analyzer. This method generates those."""
412d9cf8291SDaniel Hwang
413d9cf8291SDaniel Hwang    result = []
414d9cf8291SDaniel Hwang
415d9cf8291SDaniel Hwang    if args.constraints_model:
416*dd3c26a0STobias Hieta        result.append("-analyzer-constraints={0}".format(args.constraints_model))
417d9cf8291SDaniel Hwang    if args.internal_stats:
418*dd3c26a0STobias Hieta        result.append("-analyzer-stats")
419d9cf8291SDaniel Hwang    if args.analyze_headers:
420*dd3c26a0STobias Hieta        result.append("-analyzer-opt-analyze-headers")
421d9cf8291SDaniel Hwang    if args.stats:
422*dd3c26a0STobias Hieta        result.append("-analyzer-checker=debug.Stats")
423d9cf8291SDaniel Hwang    if args.maxloop:
424*dd3c26a0STobias Hieta        result.extend(["-analyzer-max-loop", str(args.maxloop)])
425d9cf8291SDaniel Hwang    if args.output_format:
426*dd3c26a0STobias Hieta        result.append("-analyzer-output={0}".format(args.output_format))
427d9cf8291SDaniel Hwang    if args.analyzer_config:
428*dd3c26a0STobias Hieta        result.extend(["-analyzer-config", args.analyzer_config])
429d9cf8291SDaniel Hwang    if args.verbose >= 4:
430*dd3c26a0STobias Hieta        result.append("-analyzer-display-progress")
431d9cf8291SDaniel Hwang    if args.plugins:
432*dd3c26a0STobias Hieta        result.extend(prefix_with("-load", args.plugins))
433d9cf8291SDaniel Hwang    if args.enable_checker:
434*dd3c26a0STobias Hieta        checkers = ",".join(args.enable_checker)
435*dd3c26a0STobias Hieta        result.extend(["-analyzer-checker", checkers])
436d9cf8291SDaniel Hwang    if args.disable_checker:
437*dd3c26a0STobias Hieta        checkers = ",".join(args.disable_checker)
438*dd3c26a0STobias Hieta        result.extend(["-analyzer-disable-checker", checkers])
439d9cf8291SDaniel Hwang
440*dd3c26a0STobias Hieta    return prefix_with("-Xclang", result)
441d9cf8291SDaniel Hwang
442d9cf8291SDaniel Hwang
443d9cf8291SDaniel Hwangdef require(required):
444d9cf8291SDaniel Hwang    """Decorator for checking the required values in state.
445d9cf8291SDaniel Hwang
446d9cf8291SDaniel Hwang    It checks the required attributes in the passed state and stop when
447d9cf8291SDaniel Hwang    any of those is missing."""
448d9cf8291SDaniel Hwang
449d9cf8291SDaniel Hwang    def decorator(function):
450d9cf8291SDaniel Hwang        @functools.wraps(function)
451d9cf8291SDaniel Hwang        def wrapper(*args, **kwargs):
452d9cf8291SDaniel Hwang            for key in required:
453d9cf8291SDaniel Hwang                if key not in args[0]:
454*dd3c26a0STobias Hieta                    raise KeyError(
455*dd3c26a0STobias Hieta                        "{0} not passed to {1}".format(key, function.__name__)
456*dd3c26a0STobias Hieta                    )
457d9cf8291SDaniel Hwang
458d9cf8291SDaniel Hwang            return function(*args, **kwargs)
459d9cf8291SDaniel Hwang
460d9cf8291SDaniel Hwang        return wrapper
461d9cf8291SDaniel Hwang
462d9cf8291SDaniel Hwang    return decorator
463d9cf8291SDaniel Hwang
464d9cf8291SDaniel Hwang
465*dd3c26a0STobias Hieta@require(
466*dd3c26a0STobias Hieta    [
467*dd3c26a0STobias Hieta        "command",  # entry from compilation database
468*dd3c26a0STobias Hieta        "directory",  # entry from compilation database
469*dd3c26a0STobias Hieta        "file",  # entry from compilation database
470*dd3c26a0STobias Hieta        "clang",  # clang executable name (and path)
471*dd3c26a0STobias Hieta        "direct_args",  # arguments from command line
472*dd3c26a0STobias Hieta        "force_debug",  # kill non debug macros
473*dd3c26a0STobias Hieta        "output_dir",  # where generated report files shall go
474*dd3c26a0STobias Hieta        "output_format",  # it's 'plist', 'html', 'plist-html', 'plist-multi-file', 'sarif', or 'sarif-html'
475*dd3c26a0STobias Hieta        "output_failures",  # generate crash reports or not
476*dd3c26a0STobias Hieta        "ctu",
477*dd3c26a0STobias Hieta    ]
478*dd3c26a0STobias Hieta)  # ctu control options
479d9cf8291SDaniel Hwangdef run(opts):
480d9cf8291SDaniel Hwang    """Entry point to run (or not) static analyzer against a single entry
481d9cf8291SDaniel Hwang    of the compilation database.
482d9cf8291SDaniel Hwang
483d9cf8291SDaniel Hwang    This complex task is decomposed into smaller methods which are calling
484d9cf8291SDaniel Hwang    each other in chain. If the analysis is not possible the given method
485d9cf8291SDaniel Hwang    just return and break the chain.
486d9cf8291SDaniel Hwang
487d9cf8291SDaniel Hwang    The passed parameter is a python dictionary. Each method first check
488d9cf8291SDaniel Hwang    that the needed parameters received. (This is done by the 'require'
489d9cf8291SDaniel Hwang    decorator. It's like an 'assert' to check the contract between the
490d9cf8291SDaniel Hwang    caller and the called method.)"""
491d9cf8291SDaniel Hwang
492d9cf8291SDaniel Hwang    try:
493*dd3c26a0STobias Hieta        command = opts.pop("command")
494d9cf8291SDaniel Hwang        command = command if isinstance(command, list) else decode(command)
495d9cf8291SDaniel Hwang        logging.debug("Run analyzer against '%s'", command)
496d9cf8291SDaniel Hwang        opts.update(classify_parameters(command))
497d9cf8291SDaniel Hwang
498d9cf8291SDaniel Hwang        return arch_check(opts)
499d9cf8291SDaniel Hwang    except Exception:
500d9cf8291SDaniel Hwang        logging.error("Problem occurred during analysis.", exc_info=1)
501d9cf8291SDaniel Hwang        return None
502d9cf8291SDaniel Hwang
503d9cf8291SDaniel Hwang
504*dd3c26a0STobias Hieta@require(
505*dd3c26a0STobias Hieta    [
506*dd3c26a0STobias Hieta        "clang",
507*dd3c26a0STobias Hieta        "directory",
508*dd3c26a0STobias Hieta        "flags",
509*dd3c26a0STobias Hieta        "file",
510*dd3c26a0STobias Hieta        "output_dir",
511*dd3c26a0STobias Hieta        "language",
512*dd3c26a0STobias Hieta        "error_output",
513*dd3c26a0STobias Hieta        "exit_code",
514*dd3c26a0STobias Hieta    ]
515*dd3c26a0STobias Hieta)
516d9cf8291SDaniel Hwangdef report_failure(opts):
517d9cf8291SDaniel Hwang    """Create report when analyzer failed.
518d9cf8291SDaniel Hwang
519d9cf8291SDaniel Hwang    The major report is the preprocessor output. The output filename generated
520d9cf8291SDaniel Hwang    randomly. The compiler output also captured into '.stderr.txt' file.
521d9cf8291SDaniel Hwang    And some more execution context also saved into '.info.txt' file."""
522d9cf8291SDaniel Hwang
523d9cf8291SDaniel Hwang    def extension():
524d9cf8291SDaniel Hwang        """Generate preprocessor file extension."""
525d9cf8291SDaniel Hwang
526*dd3c26a0STobias Hieta        mapping = {"objective-c++": ".mii", "objective-c": ".mi", "c++": ".ii"}
527*dd3c26a0STobias Hieta        return mapping.get(opts["language"], ".i")
528d9cf8291SDaniel Hwang
529d9cf8291SDaniel Hwang    def destination():
530d9cf8291SDaniel Hwang        """Creates failures directory if not exits yet."""
531d9cf8291SDaniel Hwang
532*dd3c26a0STobias Hieta        failures_dir = os.path.join(opts["output_dir"], "failures")
533d9cf8291SDaniel Hwang        if not os.path.isdir(failures_dir):
534d9cf8291SDaniel Hwang            os.makedirs(failures_dir)
535d9cf8291SDaniel Hwang        return failures_dir
536d9cf8291SDaniel Hwang
537d9cf8291SDaniel Hwang    # Classify error type: when Clang terminated by a signal it's a 'Crash'.
538d9cf8291SDaniel Hwang    # (python subprocess Popen.returncode is negative when child terminated
539d9cf8291SDaniel Hwang    # by signal.) Everything else is 'Other Error'.
540*dd3c26a0STobias Hieta    error = "crash" if opts["exit_code"] < 0 else "other_error"
541d9cf8291SDaniel Hwang    # Create preprocessor output file name. (This is blindly following the
542d9cf8291SDaniel Hwang    # Perl implementation.)
543*dd3c26a0STobias Hieta    (handle, name) = tempfile.mkstemp(
544*dd3c26a0STobias Hieta        suffix=extension(), prefix="clang_" + error + "_", dir=destination()
545*dd3c26a0STobias Hieta    )
546d9cf8291SDaniel Hwang    os.close(handle)
547d9cf8291SDaniel Hwang    # Execute Clang again, but run the syntax check only.
548*dd3c26a0STobias Hieta    cwd = opts["directory"]
549*dd3c26a0STobias Hieta    cmd = (
550*dd3c26a0STobias Hieta        [opts["clang"], "-fsyntax-only", "-E"]
551*dd3c26a0STobias Hieta        + opts["flags"]
552*dd3c26a0STobias Hieta        + [opts["file"], "-o", name]
553*dd3c26a0STobias Hieta    )
554d9cf8291SDaniel Hwang    try:
555d9cf8291SDaniel Hwang        cmd = get_arguments(cmd, cwd)
556d9cf8291SDaniel Hwang        run_command(cmd, cwd=cwd)
557d9cf8291SDaniel Hwang    except subprocess.CalledProcessError:
558d9cf8291SDaniel Hwang        pass
559d9cf8291SDaniel Hwang    except ClangErrorException:
560d9cf8291SDaniel Hwang        pass
561d9cf8291SDaniel Hwang    # write general information about the crash
562*dd3c26a0STobias Hieta    with open(name + ".info.txt", "w") as handle:
563*dd3c26a0STobias Hieta        handle.write(opts["file"] + os.linesep)
564*dd3c26a0STobias Hieta        handle.write(error.title().replace("_", " ") + os.linesep)
565*dd3c26a0STobias Hieta        handle.write(" ".join(cmd) + os.linesep)
566*dd3c26a0STobias Hieta        handle.write(" ".join(os.uname()) + os.linesep)
567*dd3c26a0STobias Hieta        handle.write(get_version(opts["clang"]))
568d9cf8291SDaniel Hwang        handle.close()
569d9cf8291SDaniel Hwang    # write the captured output too
570*dd3c26a0STobias Hieta    with open(name + ".stderr.txt", "w") as handle:
571*dd3c26a0STobias Hieta        handle.writelines(opts["error_output"])
572d9cf8291SDaniel Hwang        handle.close()
573d9cf8291SDaniel Hwang
574d9cf8291SDaniel Hwang
575*dd3c26a0STobias Hieta@require(
576*dd3c26a0STobias Hieta    [
577*dd3c26a0STobias Hieta        "clang",
578*dd3c26a0STobias Hieta        "directory",
579*dd3c26a0STobias Hieta        "flags",
580*dd3c26a0STobias Hieta        "direct_args",
581*dd3c26a0STobias Hieta        "file",
582*dd3c26a0STobias Hieta        "output_dir",
583*dd3c26a0STobias Hieta        "output_format",
584*dd3c26a0STobias Hieta    ]
585*dd3c26a0STobias Hieta)
586d9cf8291SDaniel Hwangdef run_analyzer(opts, continuation=report_failure):
587d9cf8291SDaniel Hwang    """It assembles the analysis command line and executes it. Capture the
588d9cf8291SDaniel Hwang    output of the analysis and returns with it. If failure reports are
589d9cf8291SDaniel Hwang    requested, it calls the continuation to generate it."""
590d9cf8291SDaniel Hwang
591d9cf8291SDaniel Hwang    def target():
592d9cf8291SDaniel Hwang        """Creates output file name for reports."""
593*dd3c26a0STobias Hieta        if opts["output_format"] in {"plist", "plist-html", "plist-multi-file"}:
594*dd3c26a0STobias Hieta            (handle, name) = tempfile.mkstemp(
595*dd3c26a0STobias Hieta                prefix="report-", suffix=".plist", dir=opts["output_dir"]
596*dd3c26a0STobias Hieta            )
597d9cf8291SDaniel Hwang            os.close(handle)
598d9cf8291SDaniel Hwang            return name
599*dd3c26a0STobias Hieta        elif opts["output_format"] in {"sarif", "sarif-html"}:
600*dd3c26a0STobias Hieta            (handle, name) = tempfile.mkstemp(
601*dd3c26a0STobias Hieta                prefix="result-", suffix=".sarif", dir=opts["output_dir"]
602*dd3c26a0STobias Hieta            )
603d9cf8291SDaniel Hwang            os.close(handle)
604d9cf8291SDaniel Hwang            return name
605*dd3c26a0STobias Hieta        return opts["output_dir"]
606d9cf8291SDaniel Hwang
607d9cf8291SDaniel Hwang    try:
608*dd3c26a0STobias Hieta        cwd = opts["directory"]
609*dd3c26a0STobias Hieta        cmd = get_arguments(
610*dd3c26a0STobias Hieta            [opts["clang"], "--analyze"]
611*dd3c26a0STobias Hieta            + opts["direct_args"]
612*dd3c26a0STobias Hieta            + opts["flags"]
613*dd3c26a0STobias Hieta            + [opts["file"], "-o", target()],
614*dd3c26a0STobias Hieta            cwd,
615*dd3c26a0STobias Hieta        )
616d9cf8291SDaniel Hwang        output = run_command(cmd, cwd=cwd)
617*dd3c26a0STobias Hieta        return {"error_output": output, "exit_code": 0}
618d9cf8291SDaniel Hwang    except subprocess.CalledProcessError as ex:
619*dd3c26a0STobias Hieta        result = {"error_output": ex.output, "exit_code": ex.returncode}
620*dd3c26a0STobias Hieta        if opts.get("output_failures", False):
621d9cf8291SDaniel Hwang            opts.update(result)
622d9cf8291SDaniel Hwang            continuation(opts)
623d9cf8291SDaniel Hwang        return result
624d9cf8291SDaniel Hwang    except ClangErrorException as ex:
625*dd3c26a0STobias Hieta        result = {"error_output": ex.error, "exit_code": 0}
626*dd3c26a0STobias Hieta        if opts.get("output_failures", False):
627d9cf8291SDaniel Hwang            opts.update(result)
628d9cf8291SDaniel Hwang            continuation(opts)
629d9cf8291SDaniel Hwang        return result
630d9cf8291SDaniel Hwang
631d9cf8291SDaniel Hwang
632d9cf8291SDaniel Hwangdef extdef_map_list_src_to_ast(extdef_src_list):
633d9cf8291SDaniel Hwang    """Turns textual external definition map list with source files into an
634d9cf8291SDaniel Hwang    external definition map list with ast files."""
635d9cf8291SDaniel Hwang
636d9cf8291SDaniel Hwang    extdef_ast_list = []
637d9cf8291SDaniel Hwang    for extdef_src_txt in extdef_src_list:
638d9cf8291SDaniel Hwang        mangled_name, path = extdef_src_txt.split(" ", 1)
639d9cf8291SDaniel Hwang        # Normalize path on windows as well
640d9cf8291SDaniel Hwang        path = os.path.splitdrive(path)[1]
641d9cf8291SDaniel Hwang        # Make relative path out of absolute
642d9cf8291SDaniel Hwang        path = path[1:] if path[0] == os.sep else path
643d9cf8291SDaniel Hwang        ast_path = os.path.join("ast", path + ".ast")
644d9cf8291SDaniel Hwang        extdef_ast_list.append(mangled_name + " " + ast_path)
645d9cf8291SDaniel Hwang    return extdef_ast_list
646d9cf8291SDaniel Hwang
647d9cf8291SDaniel Hwang
648*dd3c26a0STobias Hieta@require(["clang", "directory", "flags", "direct_args", "file", "ctu"])
649d9cf8291SDaniel Hwangdef ctu_collect_phase(opts):
650d9cf8291SDaniel Hwang    """Preprocess source by generating all data needed by CTU analysis."""
651d9cf8291SDaniel Hwang
652d9cf8291SDaniel Hwang    def generate_ast(triple_arch):
653d9cf8291SDaniel Hwang        """Generates ASTs for the current compilation command."""
654d9cf8291SDaniel Hwang
655*dd3c26a0STobias Hieta        args = opts["direct_args"] + opts["flags"]
656*dd3c26a0STobias Hieta        ast_joined_path = os.path.join(
657*dd3c26a0STobias Hieta            opts["ctu"].dir,
658*dd3c26a0STobias Hieta            triple_arch,
659*dd3c26a0STobias Hieta            "ast",
660*dd3c26a0STobias Hieta            os.path.realpath(opts["file"])[1:] + ".ast",
661*dd3c26a0STobias Hieta        )
662d9cf8291SDaniel Hwang        ast_path = os.path.abspath(ast_joined_path)
663d9cf8291SDaniel Hwang        ast_dir = os.path.dirname(ast_path)
664d9cf8291SDaniel Hwang        if not os.path.isdir(ast_dir):
665d9cf8291SDaniel Hwang            try:
666d9cf8291SDaniel Hwang                os.makedirs(ast_dir)
667d9cf8291SDaniel Hwang            except OSError:
668d9cf8291SDaniel Hwang                # In case an other process already created it.
669d9cf8291SDaniel Hwang                pass
670*dd3c26a0STobias Hieta        ast_command = [opts["clang"], "-emit-ast"]
671d9cf8291SDaniel Hwang        ast_command.extend(args)
672*dd3c26a0STobias Hieta        ast_command.append("-w")
673*dd3c26a0STobias Hieta        ast_command.append(opts["file"])
674*dd3c26a0STobias Hieta        ast_command.append("-o")
675d9cf8291SDaniel Hwang        ast_command.append(ast_path)
676d9cf8291SDaniel Hwang        logging.debug("Generating AST using '%s'", ast_command)
677*dd3c26a0STobias Hieta        run_command(ast_command, cwd=opts["directory"])
678d9cf8291SDaniel Hwang
679d9cf8291SDaniel Hwang    def map_extdefs(triple_arch):
680d9cf8291SDaniel Hwang        """Generate external definition map file for the current source."""
681d9cf8291SDaniel Hwang
682*dd3c26a0STobias Hieta        args = opts["direct_args"] + opts["flags"]
683*dd3c26a0STobias Hieta        extdefmap_command = [opts["ctu"].extdef_map_cmd]
684*dd3c26a0STobias Hieta        extdefmap_command.append(opts["file"])
685*dd3c26a0STobias Hieta        extdefmap_command.append("--")
686d9cf8291SDaniel Hwang        extdefmap_command.extend(args)
687*dd3c26a0STobias Hieta        logging.debug(
688*dd3c26a0STobias Hieta            "Generating external definition map using '%s'", extdefmap_command
689*dd3c26a0STobias Hieta        )
690*dd3c26a0STobias Hieta        extdef_src_list = run_command(extdefmap_command, cwd=opts["directory"])
691d9cf8291SDaniel Hwang        extdef_ast_list = extdef_map_list_src_to_ast(extdef_src_list)
692*dd3c26a0STobias Hieta        extern_defs_map_folder = os.path.join(
693*dd3c26a0STobias Hieta            opts["ctu"].dir, triple_arch, CTU_TEMP_DEFMAP_FOLDER
694*dd3c26a0STobias Hieta        )
695d9cf8291SDaniel Hwang        if not os.path.isdir(extern_defs_map_folder):
696d9cf8291SDaniel Hwang            try:
697d9cf8291SDaniel Hwang                os.makedirs(extern_defs_map_folder)
698d9cf8291SDaniel Hwang            except OSError:
699d9cf8291SDaniel Hwang                # In case an other process already created it.
700d9cf8291SDaniel Hwang                pass
701d9cf8291SDaniel Hwang        if extdef_ast_list:
702*dd3c26a0STobias Hieta            with tempfile.NamedTemporaryFile(
703*dd3c26a0STobias Hieta                mode="w", dir=extern_defs_map_folder, delete=False
704*dd3c26a0STobias Hieta            ) as out_file:
705d9cf8291SDaniel Hwang                out_file.write("\n".join(extdef_ast_list) + "\n")
706d9cf8291SDaniel Hwang
707*dd3c26a0STobias Hieta    cwd = opts["directory"]
708*dd3c26a0STobias Hieta    cmd = (
709*dd3c26a0STobias Hieta        [opts["clang"], "--analyze"]
710*dd3c26a0STobias Hieta        + opts["direct_args"]
711*dd3c26a0STobias Hieta        + opts["flags"]
712*dd3c26a0STobias Hieta        + [opts["file"]]
713*dd3c26a0STobias Hieta    )
714d9cf8291SDaniel Hwang    triple_arch = get_triple_arch(cmd, cwd)
715d9cf8291SDaniel Hwang    generate_ast(triple_arch)
716d9cf8291SDaniel Hwang    map_extdefs(triple_arch)
717d9cf8291SDaniel Hwang
718d9cf8291SDaniel Hwang
719*dd3c26a0STobias Hieta@require(["ctu"])
720d9cf8291SDaniel Hwangdef dispatch_ctu(opts, continuation=run_analyzer):
721d9cf8291SDaniel Hwang    """Execute only one phase of 2 phases of CTU if needed."""
722d9cf8291SDaniel Hwang
723*dd3c26a0STobias Hieta    ctu_config = opts["ctu"]
724d9cf8291SDaniel Hwang
725d9cf8291SDaniel Hwang    if ctu_config.collect or ctu_config.analyze:
726d9cf8291SDaniel Hwang        assert ctu_config.collect != ctu_config.analyze
727d9cf8291SDaniel Hwang        if ctu_config.collect:
728d9cf8291SDaniel Hwang            return ctu_collect_phase(opts)
729d9cf8291SDaniel Hwang        if ctu_config.analyze:
730*dd3c26a0STobias Hieta            cwd = opts["directory"]
731*dd3c26a0STobias Hieta            cmd = (
732*dd3c26a0STobias Hieta                [opts["clang"], "--analyze"]
733*dd3c26a0STobias Hieta                + opts["direct_args"]
734*dd3c26a0STobias Hieta                + opts["flags"]
735*dd3c26a0STobias Hieta                + [opts["file"]]
736*dd3c26a0STobias Hieta            )
737d9cf8291SDaniel Hwang            triarch = get_triple_arch(cmd, cwd)
738*dd3c26a0STobias Hieta            ctu_options = [
739*dd3c26a0STobias Hieta                "ctu-dir=" + os.path.join(ctu_config.dir, triarch),
740*dd3c26a0STobias Hieta                "experimental-enable-naive-ctu-analysis=true",
741*dd3c26a0STobias Hieta            ]
742*dd3c26a0STobias Hieta            analyzer_options = prefix_with("-analyzer-config", ctu_options)
743*dd3c26a0STobias Hieta            direct_options = prefix_with("-Xanalyzer", analyzer_options)
744*dd3c26a0STobias Hieta            opts["direct_args"].extend(direct_options)
745d9cf8291SDaniel Hwang
746d9cf8291SDaniel Hwang    return continuation(opts)
747d9cf8291SDaniel Hwang
748d9cf8291SDaniel Hwang
749*dd3c26a0STobias Hieta@require(["flags", "force_debug"])
750d9cf8291SDaniel Hwangdef filter_debug_flags(opts, continuation=dispatch_ctu):
751d9cf8291SDaniel Hwang    """Filter out nondebug macros when requested."""
752d9cf8291SDaniel Hwang
753*dd3c26a0STobias Hieta    if opts.pop("force_debug"):
754d9cf8291SDaniel Hwang        # lazy implementation just append an undefine macro at the end
755*dd3c26a0STobias Hieta        opts.update({"flags": opts["flags"] + ["-UNDEBUG"]})
756d9cf8291SDaniel Hwang
757d9cf8291SDaniel Hwang    return continuation(opts)
758d9cf8291SDaniel Hwang
759d9cf8291SDaniel Hwang
760*dd3c26a0STobias Hieta@require(["language", "compiler", "file", "flags"])
761d9cf8291SDaniel Hwangdef language_check(opts, continuation=filter_debug_flags):
762d9cf8291SDaniel Hwang    """Find out the language from command line parameters or file name
763d9cf8291SDaniel Hwang    extension. The decision also influenced by the compiler invocation."""
764d9cf8291SDaniel Hwang
765*dd3c26a0STobias Hieta    accepted = frozenset(
766*dd3c26a0STobias Hieta        {
767*dd3c26a0STobias Hieta            "c",
768*dd3c26a0STobias Hieta            "c++",
769*dd3c26a0STobias Hieta            "objective-c",
770*dd3c26a0STobias Hieta            "objective-c++",
771*dd3c26a0STobias Hieta            "c-cpp-output",
772*dd3c26a0STobias Hieta            "c++-cpp-output",
773*dd3c26a0STobias Hieta            "objective-c-cpp-output",
774*dd3c26a0STobias Hieta        }
775*dd3c26a0STobias Hieta    )
776d9cf8291SDaniel Hwang
777d9cf8291SDaniel Hwang    # language can be given as a parameter...
778*dd3c26a0STobias Hieta    language = opts.pop("language")
779*dd3c26a0STobias Hieta    compiler = opts.pop("compiler")
780d9cf8291SDaniel Hwang    # ... or find out from source file extension
781d9cf8291SDaniel Hwang    if language is None and compiler is not None:
782*dd3c26a0STobias Hieta        language = classify_source(opts["file"], compiler == "c")
783d9cf8291SDaniel Hwang
784d9cf8291SDaniel Hwang    if language is None:
785*dd3c26a0STobias Hieta        logging.debug("skip analysis, language not known")
786d9cf8291SDaniel Hwang        return None
787d9cf8291SDaniel Hwang    elif language not in accepted:
788*dd3c26a0STobias Hieta        logging.debug("skip analysis, language not supported")
789d9cf8291SDaniel Hwang        return None
790d9cf8291SDaniel Hwang    else:
791*dd3c26a0STobias Hieta        logging.debug("analysis, language: %s", language)
792*dd3c26a0STobias Hieta        opts.update({"language": language, "flags": ["-x", language] + opts["flags"]})
793d9cf8291SDaniel Hwang        return continuation(opts)
794d9cf8291SDaniel Hwang
795d9cf8291SDaniel Hwang
796*dd3c26a0STobias Hieta@require(["arch_list", "flags"])
797d9cf8291SDaniel Hwangdef arch_check(opts, continuation=language_check):
798d9cf8291SDaniel Hwang    """Do run analyzer through one of the given architectures."""
799d9cf8291SDaniel Hwang
800*dd3c26a0STobias Hieta    disabled = frozenset({"ppc", "ppc64"})
801d9cf8291SDaniel Hwang
802*dd3c26a0STobias Hieta    received_list = opts.pop("arch_list")
803d9cf8291SDaniel Hwang    if received_list:
804d9cf8291SDaniel Hwang        # filter out disabled architectures and -arch switches
805d9cf8291SDaniel Hwang        filtered_list = [a for a in received_list if a not in disabled]
806d9cf8291SDaniel Hwang        if filtered_list:
807d9cf8291SDaniel Hwang            # There should be only one arch given (or the same multiple
808d9cf8291SDaniel Hwang            # times). If there are multiple arch are given and are not
809d9cf8291SDaniel Hwang            # the same, those should not change the pre-processing step.
810d9cf8291SDaniel Hwang            # But that's the only pass we have before run the analyzer.
811d9cf8291SDaniel Hwang            current = filtered_list.pop()
812*dd3c26a0STobias Hieta            logging.debug("analysis, on arch: %s", current)
813d9cf8291SDaniel Hwang
814*dd3c26a0STobias Hieta            opts.update({"flags": ["-arch", current] + opts["flags"]})
815d9cf8291SDaniel Hwang            return continuation(opts)
816d9cf8291SDaniel Hwang        else:
817*dd3c26a0STobias Hieta            logging.debug("skip analysis, found not supported arch")
818d9cf8291SDaniel Hwang            return None
819d9cf8291SDaniel Hwang    else:
820*dd3c26a0STobias Hieta        logging.debug("analysis, on default arch")
821d9cf8291SDaniel Hwang        return continuation(opts)
822d9cf8291SDaniel Hwang
823d9cf8291SDaniel Hwang
824d9cf8291SDaniel Hwang# To have good results from static analyzer certain compiler options shall be
825d9cf8291SDaniel Hwang# omitted. The compiler flag filtering only affects the static analyzer run.
826d9cf8291SDaniel Hwang#
827d9cf8291SDaniel Hwang# Keys are the option name, value number of options to skip
828d9cf8291SDaniel HwangIGNORED_FLAGS = {
829*dd3c26a0STobias Hieta    "-c": 0,  # compile option will be overwritten
830*dd3c26a0STobias Hieta    "-fsyntax-only": 0,  # static analyzer option will be overwritten
831*dd3c26a0STobias Hieta    "-o": 1,  # will set up own output file
832d9cf8291SDaniel Hwang    # flags below are inherited from the perl implementation.
833*dd3c26a0STobias Hieta    "-g": 0,
834*dd3c26a0STobias Hieta    "-save-temps": 0,
835*dd3c26a0STobias Hieta    "-install_name": 1,
836*dd3c26a0STobias Hieta    "-exported_symbols_list": 1,
837*dd3c26a0STobias Hieta    "-current_version": 1,
838*dd3c26a0STobias Hieta    "-compatibility_version": 1,
839*dd3c26a0STobias Hieta    "-init": 1,
840*dd3c26a0STobias Hieta    "-e": 1,
841*dd3c26a0STobias Hieta    "-seg1addr": 1,
842*dd3c26a0STobias Hieta    "-bundle_loader": 1,
843*dd3c26a0STobias Hieta    "-multiply_defined": 1,
844*dd3c26a0STobias Hieta    "-sectorder": 3,
845*dd3c26a0STobias Hieta    "--param": 1,
846*dd3c26a0STobias Hieta    "--serialize-diagnostics": 1,
847d9cf8291SDaniel Hwang}
848d9cf8291SDaniel Hwang
849d9cf8291SDaniel Hwang
850d9cf8291SDaniel Hwangdef classify_parameters(command):
851d9cf8291SDaniel Hwang    """Prepare compiler flags (filters some and add others) and take out
852d9cf8291SDaniel Hwang    language (-x) and architecture (-arch) flags for future processing."""
853d9cf8291SDaniel Hwang
854d9cf8291SDaniel Hwang    result = {
855*dd3c26a0STobias Hieta        "flags": [],  # the filtered compiler flags
856*dd3c26a0STobias Hieta        "arch_list": [],  # list of architecture flags
857*dd3c26a0STobias Hieta        "language": None,  # compilation language, None, if not specified
858*dd3c26a0STobias Hieta        "compiler": compiler_language(command),  # 'c' or 'c++'
859d9cf8291SDaniel Hwang    }
860d9cf8291SDaniel Hwang
861d9cf8291SDaniel Hwang    # iterate on the compile options
862d9cf8291SDaniel Hwang    args = iter(command[1:])
863d9cf8291SDaniel Hwang    for arg in args:
864d9cf8291SDaniel Hwang        # take arch flags into a separate basket
865*dd3c26a0STobias Hieta        if arg == "-arch":
866*dd3c26a0STobias Hieta            result["arch_list"].append(next(args))
867d9cf8291SDaniel Hwang        # take language
868*dd3c26a0STobias Hieta        elif arg == "-x":
869*dd3c26a0STobias Hieta            result["language"] = next(args)
870d9cf8291SDaniel Hwang        # parameters which looks source file are not flags
871*dd3c26a0STobias Hieta        elif re.match(r"^[^-].+", arg) and classify_source(arg):
872d9cf8291SDaniel Hwang            pass
873d9cf8291SDaniel Hwang        # ignore some flags
874d9cf8291SDaniel Hwang        elif arg in IGNORED_FLAGS:
875d9cf8291SDaniel Hwang            count = IGNORED_FLAGS[arg]
876d9cf8291SDaniel Hwang            for _ in range(count):
877d9cf8291SDaniel Hwang                next(args)
878d9cf8291SDaniel Hwang        # we don't care about extra warnings, but we should suppress ones
879d9cf8291SDaniel Hwang        # that we don't want to see.
880*dd3c26a0STobias Hieta        elif re.match(r"^-W.+", arg) and not re.match(r"^-Wno-.+", arg):
881d9cf8291SDaniel Hwang            pass
882d9cf8291SDaniel Hwang        # and consider everything else as compilation flag.
883d9cf8291SDaniel Hwang        else:
884*dd3c26a0STobias Hieta            result["flags"].append(arg)
885d9cf8291SDaniel Hwang
886d9cf8291SDaniel Hwang    return result
887