diff --git a/analyzer/codechecker_analyzer/buildlog/log_parser.py b/analyzer/codechecker_analyzer/buildlog/log_parser.py index f0a325ff2e..50d776e290 100644 --- a/analyzer/codechecker_analyzer/buildlog/log_parser.py +++ b/analyzer/codechecker_analyzer/buildlog/log_parser.py @@ -1184,30 +1184,40 @@ def extend_compilation_database_entries(compilation_database): cmd = [] source_files = [] source_dir = entry['directory'] + response_files = set() options = shlex.split(entry['command']) for opt in options: if opt.startswith('@'): response_file = os.path.join(source_dir, opt[1:]) - if not os.path.exists(response_file): + source_file = os.path.join(source_dir, opt) + + if os.path.exists(response_file): + opts, sources = process_response_file(response_file) + cmd.extend([shlex.quote(x) for x in opts]) + source_files.extend(sources) + response_files.add(os.path.abspath(response_file)) + elif os.path.exists(source_file): + cmd.append(shlex.quote(opt)) + else: LOG.warning("Response file '%s' does not exists.", response_file) continue - - opts, sources = process_response_file(response_file) - cmd.extend([shlex.quote(x) for x in opts]) - source_files.extend(sources) else: cmd.append(shlex.quote(opt)) entry['command'] = ' '.join(cmd) if entry['file'].startswith('@'): - for source_file in source_files: - new_entry = dict(entry) - new_entry['file'] = source_file - yield new_entry - continue + response_file = os.path.abspath(os.path.join( + source_dir, entry['file'][1:])) + + if response_file in response_files: + for source_file in source_files: + new_entry = dict(entry) + new_entry['file'] = source_file + yield new_entry + continue yield entry diff --git a/analyzer/tests/unit/test_log_parser.py b/analyzer/tests/unit/test_log_parser.py index a920c74c2c..e2a49d4ef6 100644 --- a/analyzer/tests/unit/test_log_parser.py +++ b/analyzer/tests/unit/test_log_parser.py @@ -599,6 +599,37 @@ def test_source_file_contains_at_sign(self): build_action = build_actions[0] self.assertEqual(build_action.source, src_file_path) + def test_source_file_path_starts_with_at_sign(self): + """ + Test source file where the path starts with '@'. + + Such paths should not be handled as response files if the path itself + exists as a normal source file. + """ + src_dir = os.path.join(self.tmp_dir, "@folder") + os.mkdir(src_dir) + src_file_path = os.path.join(src_dir, "main.cpp") + + with open(src_file_path, "w", + encoding="utf-8", errors="ignore") as src_file: + src_file.write("int main() { return 0; }") + + with open(self.compile_command_file_path, "w", + encoding="utf-8", errors="ignore") as build_json: + build_json.write(json.dumps([{ + "directory": self.tmp_dir, + "command": "g++ @folder/main.cpp", + "file": "@folder/main.cpp" + }])) + + build_actions, _ = log_parser.parse_unique_log(load_json( + self.compile_command_file_path)) + + self.assertEqual(len(build_actions), 1) + + build_action = build_actions[0] + self.assertEqual(build_action.source, src_file_path) + def test_symlink(self): """ Test if each source file is analyzed exclusively once,