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