From 795117eea9ac27d103b9d4961d789b8897e7b696 Mon Sep 17 00:00:00 2001 From: bruntib Date: Fri, 16 Aug 2024 11:40:56 +0200 Subject: [PATCH] [feat] Implicit include paths added with -idirafter GCC has implicit include paths that are forwarded to Clang. Until now these paths were added with -isystem flag, but sometimes the priority of this is too high: https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html The implicit include paths should be searched the last, so -isystem has been changed to -idirafter. In case of potential backward compatibility break --add-gcc-include-dirs-with-isystem has been introduced for "CodeChecker analyze" command. --- .../analyzers/clangsa/analyzer.py | 6 +- .../analyzers/clangsa/ctu_triple_arch.py | 5 +- .../analyzers/clangsa/statistics.py | 5 +- .../analyzers/clangtidy/analyzer.py | 6 +- analyzer/codechecker_analyzer/cmd/analyze.py | 11 ++++ analyzer/codechecker_analyzer/cmd/check.py | 11 ++++ .../tests/functional/analyze/test_analyze.py | 2 +- analyzer/tests/libtest/cmd_line.py | 33 ++++++++++ analyzer/tests/unit/test_analyzer_command.py | 65 +++++++++++++++++++ analyzer/tests/unit/test_checker_handling.py | 34 ++++------ docs/analyzer/user_guide.md | 20 ++++++ 11 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 analyzer/tests/libtest/cmd_line.py create mode 100644 analyzer/tests/unit/test_analyzer_command.py diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py index 68cad88703..2670c99211 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py @@ -475,7 +475,8 @@ def construct_analyzer_cmd(self, result_handler): analyzer_cmd.extend(self.buildaction.analyzer_options) analyzer_cmd.extend(prepend_all( - '-isystem', + '-isystem' if config.add_gcc_include_dirs_with_isystem else + '-idirafter', self.buildaction.compiler_includes)) analyzer_cmd.append(self.source_file) @@ -620,6 +621,9 @@ def construct_config_handler(cls, args): handler.enable_z3_refutation = 'enable_z3_refutation' in args and \ args.enable_z3_refutation == 'on' + handler.add_gcc_include_dirs_with_isystem = \ + args.add_gcc_include_dirs_with_isystem + if 'ctu_phases' in args: handler.ctu_dir = os.path.join(args.output_path, args.ctu_dir) diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py b/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py index 3d2650c20f..a00ee828a6 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py @@ -35,7 +35,10 @@ def get_compile_command(action, config, source='', output=''): # -isystem, etc. flags where it is found. For this reason we append the # implicit include paths to the end of the analyzer command in order to get # less precedence than the user's explicit include paths. - cmd.extend(prepend_all('-isystem', action.compiler_includes)) + cmd.extend(prepend_all( + '-isystem' if config.add_gcc_include_dirs_with_isystem else + '-idirafter', + action.compiler_includes)) if output: cmd.extend(['-o', output]) if source: diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py b/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py index 46a3b7cca1..d6e83e0902 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py @@ -73,7 +73,10 @@ def build_stat_coll_cmd(action, config, source): if not has_flag('-std', cmd) and not has_flag('--std', cmd): cmd.append(action.compiler_standard) - cmd.extend(prepend_all('-isystem', action.compiler_includes)) + cmd.extend(prepend_all( + '-isystem' if config.add_gcc_include_dirs_with_isystem else + '-idirafter', + action.compiler_includes)) if source: cmd.append(source) diff --git a/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py b/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py index 11ff7d1e16..352ee621b6 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py @@ -489,7 +489,8 @@ def construct_analyzer_cmd(self, result_handler): analyzer_cmd.extend(self.buildaction.analyzer_options) analyzer_cmd.extend(prepend_all( - '-isystem', + '-isystem' if config.add_gcc_include_dirs_with_isystem else + '-idirafter', self.buildaction.compiler_includes)) if not has_flag('-std', analyzer_cmd) and not \ @@ -585,6 +586,9 @@ def construct_config_handler(cls, args): handler.report_hash = args.report_hash \ if 'report_hash' in args else None + handler.add_gcc_include_dirs_with_isystem = \ + args.add_gcc_include_dirs_with_isystem + # FIXME We cannot get the resource dir from the clang-tidy binary, # therefore we get a sibling clang binary which of clang-tidy. # TODO Support "clang-tidy -print-resource-dir" . diff --git a/analyzer/codechecker_analyzer/cmd/analyze.py b/analyzer/codechecker_analyzer/cmd/analyze.py index e2116cdba7..72faeb4be7 100644 --- a/analyzer/codechecker_analyzer/cmd/analyze.py +++ b/analyzer/codechecker_analyzer/cmd/analyze.py @@ -243,6 +243,17 @@ def add_arguments_to_parser(parser): "fails with error message related to __builtin " "symbols.") + parser.add_argument('--add-gcc-include-dirs-with-isystem', + dest="add_gcc_include_dirs_with_isystem", + required=False, + action='store_true', + default=False, + help="Implicit include directories are appended to " + "the analyzer command with -idirafter. If " + "-isystem is needed instead, as it was used " + "before CodeChecker 6.24.1, this flag can be " + "used.") + parser.add_argument('-t', '--type', '--output-format', dest="output_format", required=False, diff --git a/analyzer/codechecker_analyzer/cmd/check.py b/analyzer/codechecker_analyzer/cmd/check.py index 2e20d19e6d..ea22034f50 100644 --- a/analyzer/codechecker_analyzer/cmd/check.py +++ b/analyzer/codechecker_analyzer/cmd/check.py @@ -152,6 +152,17 @@ def add_arguments_to_parser(parser): "fails with error message related to __builtin " "symbols.") + parser.add_argument('--add-gcc-include-dirs-with-isystem', + dest="add_gcc_include_dirs_with_isystem", + required=False, + action='store_true', + default=False, + help="Implicit include directories are appended to " + "the analyzer command with -idirafter. If " + "-isystem is needed instead, as it was used " + "before CodeChecker 6.24.1, this flag can be " + "used.") + log_args = parser.add_argument_group( "log arguments", """ diff --git a/analyzer/tests/functional/analyze/test_analyze.py b/analyzer/tests/functional/analyze/test_analyze.py index 478ea84a42..952e667a30 100644 --- a/analyzer/tests/functional/analyze/test_analyze.py +++ b/analyzer/tests/functional/analyze/test_analyze.py @@ -263,7 +263,7 @@ def test_compiler_info_file_is_loaded(self): print(out) self.assertTrue("-std=FAKE_STD" in out) self.assertTrue("--target=FAKE_TARGET" in out) - self.assertTrue("-isystem /FAKE_INCLUDE_DIR" in out) + self.assertTrue("-idirafter /FAKE_INCLUDE_DIR" in out) def test_capture_analysis_output(self): """ diff --git a/analyzer/tests/libtest/cmd_line.py b/analyzer/tests/libtest/cmd_line.py new file mode 100644 index 0000000000..258f72c459 --- /dev/null +++ b/analyzer/tests/libtest/cmd_line.py @@ -0,0 +1,33 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- + +import argparse +from codechecker_analyzer.cmd import analyze + + +class NoExitArgumentParser(argparse.ArgumentParser): + """ + ArgumentParser that does not exit on error. + """ + def error(self, _): + pass + + +def create_analyze_argparse(args=None): + """ + Create argparse object for analyze command. + + :param args: list of command line arguments to parse. + """ + if args is None: + args = [] + + parser = NoExitArgumentParser() + analyze.add_arguments_to_parser(parser) + + return parser.parse_args(args) diff --git a/analyzer/tests/unit/test_analyzer_command.py b/analyzer/tests/unit/test_analyzer_command.py new file mode 100644 index 0000000000..28ca23eabd --- /dev/null +++ b/analyzer/tests/unit/test_analyzer_command.py @@ -0,0 +1,65 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- + +import argparse +import unittest +from codechecker_analyzer.analyzers.clangsa.analyzer import ClangSA +from codechecker_analyzer.buildlog import log_parser +from codechecker_analyzer.cmd import analyze +from libtest.cmd_line import create_analyze_argparse + + +def create_analyzer_sa(args=None): + parser = argparse.ArgumentParser() + analyze.add_arguments_to_parser(parser) + cfg_handler = ClangSA.construct_config_handler( + create_analyze_argparse(args)) + + action = { + 'file': 'main.cpp', + 'command': "g++ -o main main.cpp", + 'directory': '/'} + build_action = log_parser.parse_options(action) + + return ClangSA(cfg_handler, build_action) + + +def create_result_handler(analyzer): + """ + Create result handler for construct_analyzer_cmd call. + """ + + build_action = analyzer.buildaction + + rh = analyzer.construct_result_handler( + build_action, + build_action.directory, + None) + + rh.analyzed_source_file = build_action.source + + return rh + + +class AnalyzerCommandClangSATest(unittest.TestCase): + def test_isystem_idirafter(self): + """ + Test that the implicit include paths are added to the analyzer command + with -idirafter. + """ + analyzer = create_analyzer_sa(['--add-gcc-include-dirs-with-isystem']) + + result_handler = create_result_handler(analyzer) + cmd = analyzer.construct_analyzer_cmd(result_handler) + self.assertIn('-isystem', cmd) + + analyzer = create_analyzer_sa() + + result_handler = create_result_handler(analyzer) + cmd = analyzer.construct_analyzer_cmd(result_handler) + self.assertIn('-idirafter', cmd) diff --git a/analyzer/tests/unit/test_checker_handling.py b/analyzer/tests/unit/test_checker_handling.py index 0deebdf4a5..f85a45f548 100644 --- a/analyzer/tests/unit/test_checker_handling.py +++ b/analyzer/tests/unit/test_checker_handling.py @@ -31,6 +31,8 @@ from codechecker_analyzer import analyzer_context from codechecker_analyzer.buildlog import log_parser +from libtest.cmd_line import create_analyze_argparse + class MockClangsaCheckerLabels: def checkers_by_labels(self, labels): @@ -68,8 +70,7 @@ def checkers(self, _=None): def create_analyzer_sa(): - args = [] - cfg_handler = ClangSA.construct_config_handler(args) + cfg_handler = ClangSA.construct_config_handler(create_analyze_argparse()) action = { 'file': 'main.cpp', @@ -146,7 +147,7 @@ def f(checks, checkers): return set(checkers) <= result return f - args = [] + args = create_analyze_argparse() # "security" profile, but alpha -> not in default. security_profile_alpha = [ @@ -312,10 +313,8 @@ def occurring_values(self, label): def create_analyzer_tidy(args=None): - if args is None: - args = [] - - cfg_handler = ClangTidy.construct_config_handler(args) + cfg_handler = ClangTidy.construct_config_handler( + create_analyze_argparse(args)) action = { 'file': 'main.cpp', @@ -409,9 +408,7 @@ def test_disable_clangsa_checkers(self): self.assertTrue(is_compiler_warning('Wreserved-id-macro')) self.assertFalse(is_compiler_warning('hicpp')) - args = Namespace() - args.ordered_checkers = [('Wreserved-id-macro', True)] - analyzer = create_analyzer_tidy(args) + analyzer = create_analyzer_tidy(['--enable', 'Wreserved-id-macro']) result_handler = create_result_handler(analyzer) analyzer.config_handler.checker_config = '{}' @@ -472,11 +469,9 @@ def test_enable_all_disable_warning(self): Side note: we use -Weverything instead of listing all enabled warnings to represent --enable-all. """ - args = Namespace() - args.ordered_checkers = [('clang-diagnostic-unused-variable', False)] - args.enable_all = True - - analyzer = create_analyzer_tidy(args) + analyzer = create_analyzer_tidy([ + '--enable-all', + '--disable', 'clang-diagnostic-unused-variable']) result_handler = create_result_handler(analyzer) analyzer_cmd = analyzer.construct_analyzer_cmd(result_handler) @@ -514,13 +509,10 @@ def test_clang_diags_as_compiler_warnings(self): Test that clang-diagnostic-* checkers are enabled as compiler warnings. """ - args = Namespace() - args.ordered_checkers = [ + analyzer = create_analyzer_tidy([ # This should enable -Wvla and -Wvla-extension. - ('clang-diagnostic-vla', True), - ('clang-diagnostic-unused-value', False) - ] - analyzer = create_analyzer_tidy(args) + '--enable', 'clang-diagnostic-vla', + '--disable', 'clang-diagnostic-unused-value']) result_handler = create_result_handler(analyzer) analyzer.config_handler.checker_config = '{}' diff --git a/docs/analyzer/user_guide.md b/docs/analyzer/user_guide.md index b19a90360f..1a34ec3d3d 100644 --- a/docs/analyzer/user_guide.md +++ b/docs/analyzer/user_guide.md @@ -132,6 +132,7 @@ subcommand. ``` usage: CodeChecker check [-h] [-o OUTPUT_DIR] [-t {plist}] [-q] [--keep-gcc-include-fixed] [--keep-gcc-intrin] + [--add-gcc-include-dirs-with-isystem] (-b COMMAND | -l LOGFILE) [-j JOBS] [-c] [--compile-uniqueing COMPILE_UNIQUEING] [--report-hash {context-free,context-free-v2,diagnostic-message}] @@ -179,6 +180,11 @@ optional arguments: be kept among the implicit include paths. Use this flag if Clang analysis fails with error message related to __builtin symbols. (default: False) + --add-gcc-include-dirs-with-isystem + Implicit include directories are appended to the + analyzer command with -idirafter. If -isystem is needed + instead, as it was used before CodeChecker 6.24.1, this + flag can be used. (default: False) --compile-uniqueing COMPILE_UNIQUEING Specify the method the compilation actions in the compilation database are uniqued before analysis. CTU @@ -921,6 +927,7 @@ usage: CodeChecker analyze [-h] [-j JOBS] OUTPUT_PATH [--compiler-info-file COMPILER_INFO_FILE] [--keep-gcc-include-fixed] [--keep-gcc-intrin] + [--add-gcc-include-dirs-with-isystem] [-t {plist}] [-q] [-c] [--compile-uniqueing COMPILE_UNIQUEING] [--report-hash {context-free,context-free-v2,diagnostic-message}] @@ -980,6 +987,11 @@ optional arguments: be kept among the implicit include paths. Use this flag if Clang analysis fails with error message related to __builtin symbols. (default: False) + --add-gcc-include-dirs-with-isystem + Implicit include directories are appended to the + analyzer command with -idirafter. If -isystem is needed + instead, as it was used before CodeChecker 6.24.1, this + flag can be used. (default: False) -t {plist}, --type {plist}, --output-format {plist} Specify the format the analysis results should use. (default: plist) @@ -1374,6 +1386,14 @@ analysis unless `--keep-gcc-include-fixed` or `--keep-gcc-intrin` flag is given. For further information see [GCC incompatibilities](gcc_incompatibilities.md). +The GCC compiler's implicit include directories are appended to the analyzer +command with `-idirafter`. There are other flags which can be used instead of +`-idirafter`, such as , `-I`, `-isystem`, etc. They have a +[priority order](https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html) in +which compilers search header files. Prior to CodeChecker 6.24.1, the +`-isystem` flag was used instead of `-idirafter`. If you need to the `-isystem` +flag, you can use the `--add-gcc-include-dirs-with-isystem` flag. + #### Toggling checkers The list of checkers to be used in the analysis can be fine-tuned with the