def test_old_ldlogger(self): """ Test log file parsing escape behaviour with pre-2017 Q2 LD-LOGGER. """ logfile = os.path.join(self.__test_files, "ldlogger-old.json") # LD-LOGGER before http://github.com/Ericsson/codechecker/pull/631 # used an escape mechanism that, when parsed by the log parser via # shlex, made CodeChecker parse arguments with multiword string # literals in them be considered as "file" (instead of compile option), # eventually ignored by the command builder, thus lessening analysis # accuracy, as defines were lost. # # Logfile contains "-DVARIABLE="some value"". # # There is no good way to back-and-forth convert in log_parser or # option_parser, so here we aim for a non-failing stalemate of the # define being considered a file and ignored, for now. build_action = log_parser.parse_log(logfile, ParseLogOptions())[0] results = option_parser.parse_options(build_action.original_command) self.assertEqual(' '.join(results.files), r'"-DVARIABLE="some value"" /tmp/a.cpp') self.assertEqual(len(build_action.analyzer_options), 0)
def test_compile_with_include_paths(self): """ sysroot should be detected as compiler option because it is needed for the analyzer too to search for headers. """ source_files = ["main.cpp", "test.cpp"] compiler_options = [ "-std=c++11", "-include/include/myheader.h", "-include /include/myheader2.h", "--include", "/include/myheader3.h", "--sysroot", "/home/sysroot", "--sysroot=/home/sysroot3", "-isysroot /home/isysroot", "-isysroot/home/isysroot2", "-I/home/test", "-I /home/test2", "-idirafter /dirafter1", "-idirafter/dirafter2" ] linker_options = ["-L/home/test_lib", "-lm"] build_cmd = "g++ -o myapp " + \ ' '.join(compiler_options) + ' ' + \ ' '.join(linker_options) + ' ' + \ ' '.join(source_files) res = option_parser.parse_options(build_cmd) print(res) co_no_space = [] for c in compiler_options: co_no_space.append(c.replace(" ", "")) print(set(co_no_space)) print(set(res.compile_opts)) self.assertTrue(set(source_files) == set(res.files)) self.assertTrue(set(co_no_space) == set(res.compile_opts)) self.assertTrue(set(linker_options) == set(res.link_opts)) self.assertEqual(ActionType.COMPILE, res.action)
def test_link_only_multiple_files(self): """ Should be link if only object files are in the command. """ build_cmd = "g++ -o fubar foo.o main.o bar.o -lm" res = option_parser.parse_options(build_cmd) print(res) self.assertEquals(ActionType.LINK, res.action)
def test_ignore_flags(self): ignore = [ "-Werror", "-MT hello", "-M", "-fsyntax-only", "-mfloat-gprs=double", "-mfloat-gprs=yes" ] build_cmd = "g++ {} main.cpp".format(' '.join(ignore)) res = option_parser.parse_options(build_cmd) self.assertEqual(res.compile_opts, ["-fsyntax-only"])
def test_ignore_flags(self): ignore = [ "-Werror", "-MT hello", "-M", "-fsyntax-only", "-mfloat-gprs=double", "-mfloat-gprs=yes", "-mabi=spe", "-mabi=eabi", '-Xclang', '-mllvm', '-Xclang', '-instcombine-lower-dbg-declare=0' ] build_cmd = "g++ {} main.cpp".format(' '.join(ignore)) res = option_parser.parse_options(build_cmd) self.assertEqual(res.compile_opts, ["-fsyntax-only"])
def test_compile_onefile(self): """ Test the compiler command of one file. """ source_files = ["main.cpp"] build_cmd = "g++ -c " + ' '.join(source_files) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(source_files) == set(res.files)) self.assertEquals(ActionType.COMPILE, res.action)
def test_build_multiplefiles(self): """ Test the build command of multiple files. """ source_files = ["lib.cpp", "main.cpp"] build_cmd = "g++ -o main " + ' '.join(source_files) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(source_files) == set(res.files)) self.assertEquals(ActionType.COMPILE, res.action)
def test_build_onefile(self): """ Test the build command of a simple file. """ source_files = ["main.cpp"] build_cmd = "g++ -o main " + ' '.join(source_files) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(source_files) == set(res.files)) self.assertTrue(ActionType.COMPILE, res.action) self.assertEquals(0, len(res.compile_opts))
def test_preprocess_onefile(self): """ Test the preprocess command of one file. """ source_files = ["main.c"] build_cmd = "gcc -E " + ' '.join(source_files) print(build_cmd) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(source_files) == set(res.files)) self.assertEqual(ActionType.PREPROCESS, res.action)
def test_compiler_toolchain(self): """ Test if compiler toolchain is parsed and forwarded properly. """ source_files = ["main.cpp"] compiler_options = ["--gcc-toolchain=/home/user/mygcctoolchain"] build_cmd = "g++ -c " + \ ' '.join(compiler_options) + ' ' + \ ' '.join(source_files) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(compiler_options) == set(res.compile_opts)) self.assertEqual(ActionType.COMPILE, res.action)
def test_compile_optimized(self): """ Test if the compilation arguments is detected correctly from the command line. """ source_files = ["main.cpp"] compiler_options = ["-O3"] build_cmd = "g++ -c " + \ ' '.join(compiler_options) + ' ' + \ ' '.join(source_files) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(compiler_options) == set(res.compile_opts)) self.assertEqual(ActionType.COMPILE, res.action)
def test_compile_arch(self): """ Test if the compilation architecture is detected correctly from the command line. """ source_files = ["main.c"] arch = 'x86_64' build_cmd = "gcc -c -arch " + arch + " " + ' '.join(source_files) print(build_cmd) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(source_files) == set(res.files)) self.assertEqual(arch, res.arch) self.assertEqual(ActionType.COMPILE, res.action)
def test_compile_lang(self): """ Test if the compilation language is detected correctly from the command line. """ source_files = ["main.c"] lang = 'c' build_cmd = "gcc -c -x " + lang + " " + ' '.join(source_files) print(build_cmd) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(source_files) == set(res.files)) self.assertEqual(lang, res.lang) self.assertEqual(ActionType.COMPILE, res.action)
def test_link_with_include_paths(self): """ Should be link if only object files are in the command. """ object_files = ["foo.o", "main.o", "bar.o"] compiler_options = [ "--sysroot", "/home/sysroot", "-isysroot/home/isysroot", "-I/home/test" ] linker_options = ["-L/home/test_lib", "-lm"] build_cmd = "g++ -o fubar " + \ ' '.join(compiler_options) + ' ' + \ ' '.join(linker_options) + ' ' + \ ' '.join(object_files) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(object_files) == set(res.files)) self.assertTrue(set(compiler_options) == set(res.compile_opts)) self.assertTrue(set(linker_options) == set(res.link_opts)) self.assertEqual(ActionType.LINK, res.action)
def test_compile_with_include_paths(self): """ sysroot should be detected as compiler option because it is needed for the analyzer too to search for headers. """ source_files = ["main.cpp", "test.cpp"] compiler_options = ["-sysroot", "/home/sysroot", "-isysroot", "/home/isysroot", "-I/home/test"] linker_options = ["-L/home/test_lib", "-lm"] build_cmd = "g++ -o myapp " + \ ' '.join(compiler_options) + ' ' + \ ' '.join(linker_options) + ' ' + \ ' '.join(source_files) res = option_parser.parse_options(build_cmd) print(res) self.assertTrue(set(source_files) == set(res.files)) self.assertTrue(set(compiler_options) == set(res.compile_opts)) self.assertTrue(set(linker_options) == set(res.link_opts)) self.assertEqual(ActionType.COMPILE, res.action)
def parse_compile_commands_json(logfile, add_compiler_defaults=False): import json LOG.debug('parse_compile_commands_json: ' + str(add_compiler_defaults)) actions = [] filtered_build_actions = {} logfile.seek(0) data = json.load(logfile) compiler_defines = {} compiler_includes = {} counter = 0 for entry in data: sourcefile = entry['file'] lang = option_parser.get_language(sourcefile[sourcefile.rfind('.'):]) if not lang: continue action = build_action.BuildAction(counter) command = entry['command'] results = option_parser.parse_options(command) action.original_command = command action.analyzer_options = results.compile_opts action.lang = results.lang action.target = results.arch # store the compiler built in include paths # and defines if add_compiler_defaults and results.compiler: if not (results.compiler in compiler_defines): compiler_defines[results.compiler] = \ get_compiler_defines(results.compiler) compiler_includes[results.compiler] = \ get_compiler_includes(results.compiler) action.compiler_defines = compiler_defines[results.compiler] action.compiler_includes = compiler_includes[results.compiler] if results.action == option_parser.ActionType.COMPILE or \ results.action == option_parser.ActionType.LINK: action.skip = False # TODO: check arch. action.directory = entry['directory'] action.sources = sourcefile # Filter out duplicate compilation commands. unique_key = action.cmp_key if filtered_build_actions.get(unique_key) is None: filtered_build_actions[unique_key] = action del action counter += 1 for ba_hash, ba in filtered_build_actions.items(): actions.append(ba) return actions
def parse_compile_commands_json(logfile, add_compiler_defaults=False): import json LOG.debug('parse_compile_commands_json: ' + str(add_compiler_defaults)) actions = [] filtered_build_actions = {} logfile.seek(0) data = json.load(logfile) compiler_defines = {} compiler_includes = {} counter = 0 for entry in data: sourcefile = entry['file'] if not os.path.isabs(sourcefile): # Newest versions of intercept-build can create the 'file' in the # JSON Compilation Database as a relative path. sourcefile = os.path.join(os.path.abspath(entry['directory']), sourcefile) lang = option_parser.get_language(sourcefile[sourcefile.rfind('.'):]) if not lang: continue action = build_action.BuildAction(counter) if 'command' in entry: command = entry['command'] # Old versions of intercept-build (confirmed to those shipping # with upstream clang-5.0) do escapes in another way: # -DVARIABLE="a b" becomes -DVARIABLE=\"a b\" in the output. # This would be messed up later on by options_parser, so need a # fix here. (Should be removed once we are sure noone uses this # intercept-build anymore!) if r'\"' in command: command = command.replace(r'\"', '"') elif 'arguments' in entry: # Newest versions of intercept-build create an argument vector # instead of a command string. command = ' '.join(entry['arguments']) else: raise KeyError("No valid 'command' or 'arguments' entry found!") results = option_parser.parse_options(command) action.original_command = command action.analyzer_options = results.compile_opts action.lang = results.lang action.target = results.arch # Store the compiler built in include paths and defines. if add_compiler_defaults and results.compiler: if not (results.compiler in compiler_defines): # Fetch defaults from the compiler, # make sure we use the correct architecture. extra_opts = [] for regex in COMPILE_OPTS_FWD_TO_DEFAULTS_GETTER: pattern = re.compile(regex) for comp_opt in action.analyzer_options: if re.match(pattern, comp_opt): extra_opts.append(comp_opt) compiler_defines[results.compiler] = \ get_compiler_defines(results.compiler, extra_opts) compiler_includes[results.compiler] = \ get_compiler_includes(results.compiler, extra_opts) action.compiler_defines = compiler_defines[results.compiler] action.compiler_includes = compiler_includes[results.compiler] if results.action == option_parser.ActionType.COMPILE or \ results.action == option_parser.ActionType.LINK: action.skip = False # TODO: Check arch. action.directory = entry['directory'] action.sources = sourcefile # Filter out duplicate compilation commands. unique_key = action.cmp_key if filtered_build_actions.get(unique_key) is None: filtered_build_actions[unique_key] = action del action counter += 1 for ba_hash, ba in filtered_build_actions.items(): actions.append(ba) return actions
def parse_compile_commands_json(logfile, parseLogOptions): """ logfile: is a compile command json """ output_path = parseLogOptions.output_path if output_path is not None: remove_file_if_exists( os.path.join(output_path, compiler_includes_dump_file)) remove_file_if_exists( os.path.join(output_path, compiler_target_dump_file)) actions = [] filtered_build_actions = {} data = json.load(logfile) compiler_includes = {} compiler_target = {} counter = 0 for entry in data: sourcefile = entry['file'] if not os.path.isabs(sourcefile): # Newest versions of intercept-build can create the 'file' in the # JSON Compilation Database as a relative path. sourcefile = os.path.join(os.path.abspath(entry['directory']), sourcefile) lang = option_parser.get_language(sourcefile[sourcefile.rfind('.'):]) if not lang: continue action = build_action.BuildAction(counter) if 'command' in entry: command = entry['command'] # Old versions of intercept-build (confirmed to those shipping # with upstream clang-5.0) do escapes in another way: # -DVARIABLE="a b" becomes -DVARIABLE=\"a b\" in the output. # This would be messed up later on by options_parser, so need a # fix here. (Should be removed once we are sure noone uses this # intercept-build anymore!) if r'\"' in command: command = command.replace(r'\"', '"') elif 'arguments' in entry: # Newest versions of intercept-build create an argument vector # instead of a command string. command = ' '.join(entry['arguments']) else: raise KeyError("No valid 'command' or 'arguments' entry found!") results = option_parser.parse_options(command) action.original_command = command # If the original include directory could not be found # in the filesystem, it is possible that it was provided # relative to the working directory in the compile json. compile_opts = results.compile_opts for i, opt in enumerate(compile_opts): if opt.startswith('-I'): inc_dir = opt[2:].strip() if not os.path.isdir(inc_dir): compile_opts[i] = '-I' + \ os.path.join(entry['directory'], inc_dir) action.analyzer_options = compile_opts action.lang = results.lang action.target = results.arch action.output = results.output add_compiler_defaults = True # With gcc-toolchain a non default compiler toolchain can be set. # Clang will search for include paths and libraries based on the # gcc-toolchain parameter. # Detecting extra include paths from the host compiler could # conflict with this. # For example if the compiler in the compile command is clang # and gcc-toolchain is set we will get the include paths # for clang and not for the compiler set in gcc-toolchain. # This can cause missing headers during the analysis. toolchain = gcc_toolchain.toolchain_in_args(action.analyzer_options) if toolchain: add_compiler_defaults = False # Store the compiler built in include paths and defines. if add_compiler_defaults and results.compiler: if not (results.compiler in compiler_includes): # Fetch defaults from the compiler, # make sure we use the correct architecture. extra_opts = [] for regex in COMPILE_OPTS_FWD_TO_DEFAULTS_GETTER: pattern = re.compile(regex) for comp_opt in action.analyzer_options: if re.match(pattern, comp_opt): extra_opts.append(comp_opt) compiler_includes[results.compiler] = \ get_compiler_includes(parseLogOptions, results.compiler, results.lang, results.compile_opts, extra_opts) if not (results.compiler in compiler_target): compiler_target[results.compiler] = \ get_compiler_target(parseLogOptions, results.compiler) action.compiler_includes = compiler_includes[results.compiler] action.target = compiler_target[results.compiler] if results.action != option_parser.ActionType.COMPILE: action.skip = True # TODO: Check arch. action.directory = entry['directory'] action.sources = sourcefile # Filter out duplicate compilation commands. unique_key = action.cmp_key if filtered_build_actions.get(unique_key) is None: filtered_build_actions[unique_key] = action del action counter += 1 for _, ba in filtered_build_actions.items(): actions.append(ba) return actions