diff --git a/.ci/compute_projects.py b/.ci/compute_projects.py index 2dc5629328457..8567552fa25a6 100644 --- a/.ci/compute_projects.py +++ b/.ci/compute_projects.py @@ -49,8 +49,7 @@ "flang", }, "lld": {"bolt", "cross-project-tests"}, - # TODO(issues/132795): LLDB should be enabled on clang changes. - "clang": {"clang-tools-extra", "cross-project-tests"}, + "clang": {"clang-tools-extra", "cross-project-tests", "lldb"}, "mlir": {"flang"}, # Test everything if ci scripts are changed. ".ci": { @@ -80,6 +79,7 @@ "clang": {"compiler-rt"}, "clang-tools-extra": {"libc"}, "libc": {"libc"}, + "compiler-rt": {"compiler-rt"}, ".ci": {"compiler-rt", "libc"}, } DEPENDENT_RUNTIMES_TO_TEST_NEEDS_RECONFIG = { @@ -100,6 +100,9 @@ "libc", # No Windows Support. "lldb", # TODO(issues/132800): Needs environment setup. "bolt", # No Windows Support. + "libcxx", + "libcxxabi", + "libunwind", } # These are projects that we should test if the project itself is changed but @@ -118,6 +121,9 @@ "lldb", "openmp", "polly", + "libcxx", + "libcxxabi", + "libunwind", } PROJECT_CHECK_TARGETS = { diff --git a/.ci/compute_projects_test.py b/.ci/compute_projects_test.py index bd761198300a8..7d780b51ca5d1 100644 --- a/.ci/compute_projects_test.py +++ b/.ci/compute_projects_test.py @@ -45,16 +45,14 @@ def test_llvm_windows(self): env_variables["project_check_targets"], "check-clang check-clang-tools check-lld check-llvm check-mlir check-polly", ) - self.assertEqual( - env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind" - ) + self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual( env_variables["runtimes_check_targets"], "", ) self.assertEqual( env_variables["runtimes_check_targets_needs_reconfig"], - "check-cxx check-cxxabi check-unwind", + "", ) def test_llvm_mac(self): @@ -69,16 +67,14 @@ def test_llvm_mac(self): env_variables["project_check_targets"], "check-clang check-clang-tools check-lld check-llvm check-mlir", ) - self.assertEqual( - env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind" - ) + self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual( env_variables["runtimes_check_targets"], "", ) self.assertEqual( env_variables["runtimes_check_targets_needs_reconfig"], - "check-cxx check-cxxabi check-unwind", + "", ) def test_clang(self): @@ -87,11 +83,11 @@ def test_clang(self): ) self.assertEqual( env_variables["projects_to_build"], - "clang;clang-tools-extra;lld;llvm", + "clang;clang-tools-extra;lld;lldb;llvm", ) self.assertEqual( env_variables["project_check_targets"], - "check-clang check-clang-tools", + "check-clang check-clang-tools check-lldb", ) self.assertEqual( env_variables["runtimes_to_build"], "compiler-rt;libcxx;libcxxabi;libunwind" @@ -119,30 +115,54 @@ def test_clang_windows(self): self.assertEqual( env_variables["project_check_targets"], "check-clang check-clang-tools" ) - self.assertEqual( - env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind" - ) + self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual( env_variables["runtimes_check_targets"], "", ) self.assertEqual( env_variables["runtimes_check_targets_needs_reconfig"], - "check-cxx check-cxxabi check-unwind", + "", ) self.assertEqual(env_variables["enable_cir"], "OFF") + def test_compiler_rt(self): + env_variables = compute_projects.get_env_variables( + ["compiler-rt/lib/asan/asan_allocator.cpp"], "Linux" + ) + self.assertEqual( + env_variables["projects_to_build"], + "clang;lld", + ) + self.assertEqual( + env_variables["project_check_targets"], + "", + ) + self.assertEqual(env_variables["runtimes_to_build"], "compiler-rt") + self.assertEqual( + env_variables["runtimes_check_targets"], + "check-compiler-rt", + ) + self.assertEqual( + env_variables["runtimes_check_targets_needs_reconfig"], + "", + ) + self.assertEqual( + env_variables["enable_cir"], + "OFF", + ) + def test_cir(self): env_variables = compute_projects.get_env_variables( ["clang/lib/CIR/CMakeLists.txt"], "Linux" ) self.assertEqual( env_variables["projects_to_build"], - "clang;clang-tools-extra;lld;llvm;mlir", + "clang;clang-tools-extra;lld;lldb;llvm;mlir", ) self.assertEqual( env_variables["project_check_targets"], - "check-clang check-clang-cir check-clang-tools", + "check-clang check-clang-cir check-clang-tools check-lldb", ) self.assertEqual( env_variables["runtimes_to_build"], "compiler-rt;libcxx;libcxxabi;libunwind" @@ -298,18 +318,15 @@ def test_windows_ci(self): ) self.assertEqual( env_variables["runtimes_to_build"], - "libcxx;libcxxabi;libunwind", + "", ) self.assertEqual( env_variables["runtimes_check_targets"], "", ) - # TODO(boomanaiden154): We should not be emitting these on Windows. - # It does not currently impact anything because we do not build the - # runtimes on Windows though. self.assertEqual( env_variables["runtimes_check_targets_needs_reconfig"], - "check-cxx check-cxxabi check-unwind", + "", ) def test_lldb(self): diff --git a/.ci/generate_test_report_github.py b/.ci/generate_test_report_github.py index be43c4b4f693f..7242264723cbf 100644 --- a/.ci/generate_test_report_github.py +++ b/.ci/generate_test_report_github.py @@ -4,20 +4,25 @@ """Script to generate a build report for Github.""" import argparse +import platform import generate_test_report_lib +PLATFORM_TITLES = { + "Windows": ":window: Windows x64 Test Results", + "Linux": ":penguin: Linux x64 Test Results", +} + if __name__ == "__main__": parser = argparse.ArgumentParser() + parser.add_argument("return_code", help="The build's return code.", type=int) parser.add_argument( - "title", help="Title of the test report, without Markdown formatting." + "build_test_logs", help="Paths to JUnit report files and ninja logs.", nargs="*" ) - parser.add_argument("return_code", help="The build's return code.", type=int) - parser.add_argument("junit_files", help="Paths to JUnit report files.", nargs="*") args = parser.parse_args() report = generate_test_report_lib.generate_report_from_files( - args.title, args.return_code, args.junit_files + PLATFORM_TITLES[platform.system()], args.return_code, args.build_test_logs ) print(report) diff --git a/.ci/generate_test_report_lib.py b/.ci/generate_test_report_lib.py index 25d810f1c6d17..d868c08ab69ef 100644 --- a/.ci/generate_test_report_lib.py +++ b/.ci/generate_test_report_lib.py @@ -12,6 +12,84 @@ "https://github.com/llvm/llvm-project/issues and add the " "`infrastructure` label." ) +# The maximum number of lines to pull from a ninja failure. +NINJA_LOG_SIZE_THRESHOLD = 500 + + +def _parse_ninja_log(ninja_log: list[str]) -> list[tuple[str, str]]: + """Parses an individual ninja log.""" + failures = [] + index = 0 + while index < len(ninja_log): + while index < len(ninja_log) and not ninja_log[index].startswith("FAILED:"): + index += 1 + if index == len(ninja_log): + # We hit the end of the log without finding a build failure, go to + # the next log. + return failures + # We are trying to parse cases like the following: + # + # [4/5] test/4.stamp + # FAILED: touch test/4.stamp + # touch test/4.stamp + # + # index will point to the line that starts with Failed:. The progress + # indicator is the line before this ([4/5] test/4.stamp) and contains a pretty + # printed version of the target being built (test/4.stamp). We use this line + # and remove the progress information to get a succinct name for the target. + failing_action = ninja_log[index - 1].split("] ")[1] + failure_log = [] + while ( + index < len(ninja_log) + and not ninja_log[index].startswith("[") + and not ninja_log[index].startswith("ninja: build stopped:") + and len(failure_log) < NINJA_LOG_SIZE_THRESHOLD + ): + failure_log.append(ninja_log[index]) + index += 1 + failures.append((failing_action, "\n".join(failure_log))) + return failures + + +def find_failure_in_ninja_logs(ninja_logs: list[list[str]]) -> list[tuple[str, str]]: + """Extracts failure messages from ninja output. + + This function takes stdout/stderr from ninja in the form of a list of files + represented as a list of lines. This function then returns tuples containing + the name of the target and the error message. + + Args: + ninja_logs: A list of files in the form of a list of lines representing the log + files captured from ninja. + + Returns: + A list of tuples. The first string is the name of the target that failed. The + second string is the error message. + """ + failures = [] + for ninja_log in ninja_logs: + log_failures = _parse_ninja_log(ninja_log) + failures.extend(log_failures) + return failures + + +def _format_ninja_failures(ninja_failures: list[tuple[str, str]]) -> list[str]: + """Formats ninja failures into summary views for the report.""" + output = [] + for build_failure in ninja_failures: + failed_action, failure_message = build_failure + output.extend( + [ + "
", + f"{failed_action}", + "", + "```", + failure_message, + "```", + "
", + ] + ) + return output # Set size_limit to limit the byte size of the report. The default is 1MB as this @@ -24,6 +102,7 @@ def generate_report( title, return_code, junit_objects, + ninja_logs: list[list[str]], size_limit=1024 * 1024, list_failures=True, ): @@ -61,15 +140,34 @@ def generate_report( ] ) else: - report.extend( - [ - "The build failed before running any tests.", - "", - SEE_BUILD_FILE_STR, - "", - UNRELATED_FAILURES_STR, - ] - ) + ninja_failures = find_failure_in_ninja_logs(ninja_logs) + if not ninja_failures: + report.extend( + [ + "The build failed before running any tests. Detailed " + "information about the build failure could not be " + "automatically obtained.", + "", + SEE_BUILD_FILE_STR, + "", + UNRELATED_FAILURES_STR, + ] + ) + else: + report.extend( + [ + "The build failed before running any tests. Click on a " + "failure below to see the details.", + "", + ] + ) + report.extend(_format_ninja_failures(ninja_failures)) + report.extend( + [ + "", + UNRELATED_FAILURES_STR, + ] + ) return "\n".join(report) tests_passed = tests_run - tests_skipped - tests_failed @@ -114,14 +212,28 @@ def plural(num_tests): elif return_code != 0: # No tests failed but the build was in a failed state. Bring this to the user's # attention. - report.extend( - [ - "", - "All tests passed but another part of the build **failed**.", - "", - SEE_BUILD_FILE_STR, - ] - ) + ninja_failures = find_failure_in_ninja_logs(ninja_logs) + if not ninja_failures: + report.extend( + [ + "", + "All tests passed but another part of the build **failed**. " + "Information about the build failure could not be automatically " + "obtained.", + "", + SEE_BUILD_FILE_STR, + ] + ) + else: + report.extend( + [ + "", + "All tests passed but another part of the build **failed**. Click on " + "a failure below to see the details.", + "", + ] + ) + report.extend(_format_ninja_failures(ninja_failures)) if failures or return_code != 0: report.extend(["", UNRELATED_FAILURES_STR]) @@ -139,9 +251,19 @@ def plural(num_tests): return report -def generate_report_from_files(title, return_code, junit_files): +def generate_report_from_files(title, return_code, build_log_files): + junit_files = [ + junit_file for junit_file in build_log_files if junit_file.endswith(".xml") + ] + ninja_log_files = [ + ninja_log for ninja_log in build_log_files if ninja_log.endswith(".log") + ] + ninja_logs = [] + for ninja_log_file in ninja_log_files: + with open(ninja_log_file, "r") as ninja_log_file_handle: + ninja_logs.append( + [log_line.strip() for log_line in ninja_log_file_handle.readlines()] + ) return generate_report( - title, - return_code, - [JUnitXml.fromfile(p) for p in junit_files], + title, return_code, [JUnitXml.fromfile(p) for p in junit_files], ninja_logs ) diff --git a/.ci/generate_test_report_lib_test.py b/.ci/generate_test_report_lib_test.py index eda76ead19b9d..466a8234776dc 100644 --- a/.ci/generate_test_report_lib_test.py +++ b/.ci/generate_test_report_lib_test.py @@ -8,6 +8,8 @@ import unittest from io import StringIO from textwrap import dedent +import tempfile +import os from junitparser import JUnitXml @@ -19,9 +21,114 @@ def junit_from_xml(xml): class TestReports(unittest.TestCase): + def test_find_failure_ninja_logs(self): + failures = generate_test_report_lib.find_failure_in_ninja_logs( + [ + [ + "[1/5] test/1.stamp", + "[2/5] test/2.stamp", + "[3/5] test/3.stamp", + "[4/5] test/4.stamp", + "FAILED: touch test/4.stamp", + "Wow! This system is really broken!", + "[5/5] test/5.stamp", + ], + ] + ) + self.assertEqual(len(failures), 1) + self.assertEqual( + failures[0], + ( + "test/4.stamp", + dedent( + """\ + FAILED: touch test/4.stamp + Wow! This system is really broken!""" + ), + ), + ) + + def test_no_failure_ninja_log(self): + failures = generate_test_report_lib.find_failure_in_ninja_logs( + [ + [ + "[1/3] test/1.stamp", + "[2/3] test/2.stamp", + "[3/3] test/3.stamp", + ] + ] + ) + self.assertEqual(failures, []) + + def test_ninja_log_end(self): + failures = generate_test_report_lib.find_failure_in_ninja_logs( + [ + [ + "[1/3] test/1.stamp", + "[2/3] test/2.stamp", + "[3/3] test/3.stamp", + "FAILED: touch test/3.stamp", + "Wow! This system is really broken!", + "ninja: build stopped: subcommand failed.", + ] + ] + ) + self.assertEqual(len(failures), 1) + self.assertEqual( + failures[0], + ( + "test/3.stamp", + dedent( + """\ + FAILED: touch test/3.stamp + Wow! This system is really broken!""" + ), + ), + ) + + def test_ninja_log_multiple_failures(self): + failures = generate_test_report_lib.find_failure_in_ninja_logs( + [ + [ + "[1/5] test/1.stamp", + "[2/5] test/2.stamp", + "FAILED: touch test/2.stamp", + "Wow! This system is really broken!", + "[3/5] test/3.stamp", + "[4/5] test/4.stamp", + "FAILED: touch test/4.stamp", + "Wow! This system is maybe broken!", + "[5/5] test/5.stamp", + ] + ] + ) + self.assertEqual(len(failures), 2) + self.assertEqual( + failures[0], + ( + "test/2.stamp", + dedent( + """\ + FAILED: touch test/2.stamp + Wow! This system is really broken!""" + ), + ), + ) + self.assertEqual( + failures[1], + ( + "test/4.stamp", + dedent( + """\ + FAILED: touch test/4.stamp + Wow! This system is maybe broken!""" + ), + ), + ) + def test_title_only(self): self.assertEqual( - generate_test_report_lib.generate_report("Foo", 0, []), + generate_test_report_lib.generate_report("Foo", 0, [], []), dedent( """\ # Foo @@ -32,12 +139,12 @@ def test_title_only(self): def test_title_only_failure(self): self.assertEqual( - generate_test_report_lib.generate_report("Foo", 1, []), + generate_test_report_lib.generate_report("Foo", 1, [], []), dedent( """\ # Foo - The build failed before running any tests. + The build failed before running any tests. Detailed information about the build failure could not be automatically obtained. Download the build's log file to see the details. @@ -45,6 +152,45 @@ def test_title_only_failure(self): ), ) + def test_title_only_failure_ninja_log(self): + self.assertEqual( + generate_test_report_lib.generate_report( + "Foo", + 1, + [], + [ + [ + "[1/5] test/1.stamp", + "[2/5] test/2.stamp", + "[3/5] test/3.stamp", + "[4/5] test/4.stamp", + "FAILED: test/4.stamp", + "touch test/4.stamp", + "Wow! Risk!", + "[5/5] test/5.stamp", + ] + ], + ), + dedent( + """\ + # Foo + + The build failed before running any tests. Click on a failure below to see the details. + +
+ test/4.stamp + + ``` + FAILED: test/4.stamp + touch test/4.stamp + Wow! Risk! + ``` +
+ + If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.""" + ), + ) + def test_no_tests_in_testsuite(self): self.assertEqual( generate_test_report_lib.generate_report( @@ -62,12 +208,13 @@ def test_no_tests_in_testsuite(self): ) ) ], + [], ), dedent( """\ # Foo - The build failed before running any tests. + The build failed before running any tests. Detailed information about the build failure could not be automatically obtained. Download the build's log file to see the details. @@ -93,6 +240,7 @@ def test_no_failures(self): ) ) ], + [], ), ( dedent( @@ -122,6 +270,7 @@ def test_no_failures_build_failed(self): ) ) ], + [], ), ( dedent( @@ -130,7 +279,7 @@ def test_no_failures_build_failed(self): * 1 test passed - All tests passed but another part of the build **failed**. + All tests passed but another part of the build **failed**. Information about the build failure could not be automatically obtained. Download the build's log file to see the details. @@ -139,6 +288,155 @@ def test_no_failures_build_failed(self): ), ) + def test_no_failures_build_failed_ninja_log(self): + self.assertEqual( + generate_test_report_lib.generate_report( + "Foo", + 1, + [ + junit_from_xml( + dedent( + """\ + + + + + + """ + ) + ) + ], + [ + [ + "[1/5] test/1.stamp", + "[2/5] test/2.stamp", + "[3/5] test/3.stamp", + "[4/5] test/4.stamp", + "FAILED: test/4.stamp", + "touch test/4.stamp", + "Wow! Close To You!", + "[5/5] test/5.stamp", + ] + ], + ), + ( + dedent( + """\ + # Foo + + * 1 test passed + + All tests passed but another part of the build **failed**. Click on a failure below to see the details. + +
+ test/4.stamp + + ``` + FAILED: test/4.stamp + touch test/4.stamp + Wow! Close To You! + ``` +
+ + If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.""" + ) + ), + ) + + def test_no_failures_multiple_build_failed_ninja_log(self): + test = generate_test_report_lib.generate_report( + "Foo", + 1, + [ + junit_from_xml( + dedent( + """\ + + + + + + """ + ) + ) + ], + [ + [ + "[1/5] test/1.stamp", + "[2/5] test/2.stamp", + "FAILED: touch test/2.stamp", + "Wow! Be Kind!", + "[3/5] test/3.stamp", + "[4/5] test/4.stamp", + "FAILED: touch test/4.stamp", + "Wow! I Dare You!", + "[5/5] test/5.stamp", + ] + ], + ) + print(test) + self.assertEqual( + generate_test_report_lib.generate_report( + "Foo", + 1, + [ + junit_from_xml( + dedent( + """\ + + + + + + """ + ) + ) + ], + [ + [ + "[1/5] test/1.stamp", + "[2/5] test/2.stamp", + "FAILED: touch test/2.stamp", + "Wow! Be Kind!", + "[3/5] test/3.stamp", + "[4/5] test/4.stamp", + "FAILED: touch test/4.stamp", + "Wow! I Dare You!", + "[5/5] test/5.stamp", + ] + ], + ), + ( + dedent( + """\ + # Foo + + * 1 test passed + + All tests passed but another part of the build **failed**. Click on a failure below to see the details. + +
+ test/2.stamp + + ``` + FAILED: touch test/2.stamp + Wow! Be Kind! + ``` +
+
+ test/4.stamp + + ``` + FAILED: touch test/4.stamp + Wow! I Dare You! + ``` +
+ + If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.""" + ) + ), + ) + def test_report_single_file_single_testsuite(self): self.assertEqual( generate_test_report_lib.generate_report( @@ -166,6 +464,7 @@ def test_report_single_file_single_testsuite(self): ) ) ], + [], ), ( dedent( @@ -261,6 +560,7 @@ def test_report_single_file_multiple_testsuites(self): ) ) ], + [], ), self.MULTI_SUITE_OUTPUT, ) @@ -302,6 +602,7 @@ def test_report_multiple_files_multiple_testsuites(self): ) ), ], + [], ), self.MULTI_SUITE_OUTPUT, ) @@ -326,6 +627,7 @@ def test_report_dont_list_failures(self): ) ) ], + [], list_failures=False, ), ( @@ -362,6 +664,7 @@ def test_report_dont_list_failures_link_to_log(self): ) ) ], + [], list_failures=False, ), ( @@ -401,6 +704,7 @@ def test_report_size_limit(self): ) ) ], + [], size_limit=512, ), ( @@ -416,3 +720,59 @@ def test_report_size_limit(self): ) ), ) + + def test_generate_report_end_to_end(self): + with tempfile.TemporaryDirectory() as temp_dir: + junit_xml_file = os.path.join(temp_dir, "junit.xml") + with open(junit_xml_file, "w") as junit_xml_handle: + junit_xml_handle.write( + dedent( + """\ + + + + + + """ + ) + ) + ninja_log_file = os.path.join(temp_dir, "ninja.log") + with open(ninja_log_file, "w") as ninja_log_handle: + ninja_log_handle.write( + dedent( + """\ + [1/5] test/1.stamp + [2/5] test/2.stamp + [3/5] test/3.stamp + [4/5] test/4.stamp + FAILED: test/4.stamp + touch test/4.stamp + Wow! That's so True! + [5/5] test/5.stamp""" + ) + ) + self.assertEqual( + generate_test_report_lib.generate_report_from_files( + "Foo", 1, [junit_xml_file, ninja_log_file] + ), + dedent( + """\ + # Foo + + * 1 test passed + + All tests passed but another part of the build **failed**. Click on a failure below to see the details. + +
+ test/4.stamp + + ``` + FAILED: test/4.stamp + touch test/4.stamp + Wow! That's so True! + ``` +
+ + If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.""" + ), + ) diff --git a/.ci/metrics/metrics.py b/.ci/metrics/metrics.py index 26fdeef1913ab..0896b193b6b03 100644 --- a/.ci/metrics/metrics.py +++ b/.ci/metrics/metrics.py @@ -28,7 +28,10 @@ # Lists the Github workflows we want to track. Maps the Github job name to # the metric name prefix in grafana. # This metric name is also used as a key in the job->name map. -GITHUB_WORKFLOW_TO_TRACK = {"CI Checks": "github_llvm_premerge_checks"} +GITHUB_WORKFLOW_TO_TRACK = { + "CI Checks": "github_llvm_premerge_checks", + "Build and Test libc++": "github_libcxx_premerge_checks", +} # Lists the Github jobs to track for a given workflow. The key is the stable # name (metric name) of the workflow (see GITHUB_WORKFLOW_TO_TRACK). @@ -38,7 +41,12 @@ "github_llvm_premerge_checks": { "Build and Test Linux": "premerge_linux", "Build and Test Windows": "premerge_windows", - } + }, + "github_libcxx_premerge_checks": { + "stage1": "premerge_libcxx_stage1", + "stage2": "premerge_libcxx_stage2", + "stage3": "premerge_libcxx_stage3", + }, } # The number of workflows to pull when sampling Github workflows. @@ -62,13 +70,14 @@ # by trial and error). GRAFANA_METRIC_MAX_AGE_MN = 120 - @dataclass class JobMetrics: job_name: str queue_time: int run_time: int status: int + created_at_ns: int + started_at_ns: int completed_at_ns: int workflow_id: int workflow_name: str @@ -81,6 +90,159 @@ class GaugeMetric: time_ns: int +@dataclass +class AggregateMetric: + aggregate_name: str + aggregate_queue_time: int + aggregate_run_time: int + aggregate_status: int + completed_at_ns: int + workflow_id: int + + +def _construct_aggregate(ag_name: str, job_list: list[JobMetrics]) -> AggregateMetric: + """Create a libc++ AggregateMetric from a list of libc++ JobMetrics + + How aggregates are computed: + queue time: Time from when first job in group is created until last job in + group has started. + run time: Time from when first job in group starts running until last job + in group finishes running. + status: logical 'and' of all the job statuses in the group. + + Args: + ag_name: The name for this particular AggregateMetric + job_list: This list of JobMetrics to be combined into the AggregateMetric. + The input list should contain all (and only!) the libc++ JobMetrics + for a particular stage and a particular workflow_id. + + Returns: + Returns the AggregateMetric constructed from the inputs. + """ + + # Initialize the aggregate values + earliest_create = job_list[0].created_at_ns + earliest_start = job_list[0].started_at_ns + earliest_complete = job_list[0].completed_at_ns + latest_start = job_list[0].started_at_ns + latest_complete = job_list[0].completed_at_ns + ag_status = job_list[0].status + ag_workflow_id = job_list[0].workflow_id + + # Go through rest of jobs for this workflow id, if any, updating stats + for job in job_list[1:]: + # Update the status + ag_status = ag_status and job.status + # Get the earliest & latest times + if job.created_at_ns < earliest_create: + earliest_create = job.created_at_ns + if job.completed_at_ns < earliest_complete: + earliest_complete = job.completed_at_ns + if job.started_at_ns > latest_start: + latest_start = job.started_at_ns + if job.started_at_ns < earliest_start: + earliest_start = job.started_at_ns + if job.completed_at_ns > latest_complete: + latest_complete = job.completed_at_ns + + # Compute aggregate run time (in seconds, not ns) + ag_run_time = (latest_complete - earliest_start) / 1000000000 + # Compute aggregate queue time (in seconds, not ns) + ag_queue_time = (latest_start - earliest_create) / 1000000000 + # Append the aggregate metrics to the workflow metrics list. + return AggregateMetric( + ag_name, ag_queue_time, ag_run_time, ag_status, latest_complete, ag_workflow_id + ) + + +def create_and_append_libcxx_aggregates(workflow_metrics: list[JobMetrics]): + """Find libc++ JobMetric entries and create aggregate metrics for them. + + Sort the libc++ JobMetric entries by workflow id, and for each workflow + id group them by stages. Call _construct_aggregate to reate an aggregate + metric for each stage for each unique workflow id. Append each aggregate + metric to the input workflow_metrics list. + + Args: + workflow_metrics: A list of JobMetrics entries collected so far. + """ + # Separate the jobs by workflow_id. Only look at JobMetrics entries. + aggregate_data = dict() + for job in workflow_metrics: + # Only want to look at JobMetrics + if not isinstance(job, JobMetrics): + continue + # Only want libc++ jobs. + if job.workflow_name != "Build and Test libc++": + continue + if job.workflow_id not in aggregate_data.keys(): + aggregate_data[job.workflow_id] = [job] + else: + aggregate_data[job.workflow_id].append(job) + + # Go through each aggregate_data list (workflow id) and find all the + # needed data + for ag_workflow_id in aggregate_data: + job_list = aggregate_data[ag_workflow_id] + stage1_jobs = list() + stage2_jobs = list() + stage3_jobs = list() + # sort jobs into stage1, stage2, & stage3. + for job in job_list: + if job.job_name.find("stage1") > 0: + stage1_jobs.append(job) + elif job.job_name.find("stage2") > 0: + stage2_jobs.append(job) + elif job.job_name.find("stage3") > 0: + stage3_jobs.append(job) + + if len(stage1_jobs) > 0: + aggregate = _construct_aggregate( + "github_libcxx_premerge_checks_stage1_aggregate", stage1_jobs + ) + workflow_metrics.append(aggregate) + if len(stage2_jobs) > 0: + aggregate = _construct_aggregate( + "github_libcxx_premerge_checks_stage2_aggregate", stage2_jobs + ) + workflow_metrics.append(aggregate) + if len(stage3_jobs) > 0: + aggregate = _construct_aggregate( + "github_libcxx_premerge_checks_stage3_aggregate", stage3_jobs + ) + workflow_metrics.append(aggregate) + + +def clean_up_libcxx_job_name(old_name: str) -> str: + """Convert libcxx job names to generically legal strings. + + Args: + old_name: A string with the full name of the libc++ test that was run. + + Returns: + Returns the input string with characters that might not be acceptable + in some indentifier strings replaced with safer characters. + + Take a name like 'stage1 (generic-cxx03, clang-22, clang++-22)' + and convert it to 'stage1_generic_cxx03__clang_22__clangxx_22'. + (Remove parentheses; replace commas, hyphens and spaces with + underscores; replace '+' with 'x'.) + """ + # Names should have exactly one set of parentheses, so break on that. If + # they don't have any parentheses, then don't update them at all. + if old_name.find("(") == -1: + return old_name + stage, remainder = old_name.split("(") + stage = stage.strip() + if remainder[-1] == ")": + remainder = remainder[:-1] + remainder = remainder.replace("-", "_") + remainder = remainder.replace(",", "_") + remainder = remainder.replace(" ", "_") + remainder = remainder.replace("+", "x") + new_name = stage + "_" + remainder + return new_name + def github_get_metrics( github_repo: github.Repository, last_workflows_seen_as_completed: set[int] ) -> tuple[list[JobMetrics], int]: @@ -146,6 +308,10 @@ def github_get_metrics( if task.name not in GITHUB_WORKFLOW_TO_TRACK: continue + libcxx_testing = False + if task.name == "Build and Test libc++": + libcxx_testing = True + if task.status == "completed": workflow_seen_as_completed.add(task.id) @@ -155,11 +321,19 @@ def github_get_metrics( name_prefix = GITHUB_WORKFLOW_TO_TRACK[task.name] for job in task.jobs(): + if libcxx_testing: + # We're not running macos or windows libc++ tests on our + # infrastructure. + if job.name.find("macos") != -1 or job.name.find("windows") != -1: + continue # This job is not interesting to us. - if job.name not in GITHUB_JOB_TO_TRACK[name_prefix]: + elif job.name not in GITHUB_JOB_TO_TRACK[name_prefix]: continue - name_suffix = GITHUB_JOB_TO_TRACK[name_prefix][job.name] + if libcxx_testing: + name_suffix = clean_up_libcxx_job_name(job.name) + else: + name_suffix = GITHUB_JOB_TO_TRACK[name_prefix][job.name] metric_name = name_prefix + "_" + name_suffix if task.status != "completed": @@ -208,8 +382,13 @@ def github_get_metrics( continue logging.info(f"Adding a job metric for job {job.id} in workflow {task.id}") - # The timestamp associated with the event is expected by Grafana to be - # in nanoseconds. + # The completed_at_ns timestamp associated with the event is + # expected by Grafana to be in nanoseconds. Because we do math using + # all three times (when creating libc++ aggregates), we need them + # all to be in nanoseconds, even though created_at and started_at + # are not returned to Grafana. + created_at_ns = int(created_at.timestamp()) * 10**9 + started_at_ns = int(started_at.timestamp()) * 10**9 completed_at_ns = int(completed_at.timestamp()) * 10**9 workflow_metrics.append( JobMetrics( @@ -217,12 +396,18 @@ def github_get_metrics( queue_time.seconds, run_time.seconds, job_result, + created_at_ns, + started_at_ns, completed_at_ns, task.id, task.name, ) ) + # Finished collecting the JobMetrics for all jobs; now create the + # aggregates for any libc++ jobs. + create_and_append_libcxx_aggregates(workflow_metrics) + for name, value in queued_count.items(): workflow_metrics.append( GaugeMetric(f"workflow_queue_size_{name}", value, time.time_ns()) @@ -278,6 +463,11 @@ def upload_metrics(workflow_metrics, metrics_userid, api_key): metrics_batch.append( f"{name} queue_time={workflow_metric.queue_time},run_time={workflow_metric.run_time},status={workflow_metric.status} {workflow_metric.completed_at_ns}" ) + elif isinstance(workflow_metric, AggregateMetric): + name = workflow_metric.aggregate_name.lower().replace(" ", "_") + metrics_batch.append( + f"{name} queue_time={workflow_metric.aggregate_queue_time},run_time={workflow_metric.aggregate_run_time},status={workflow_metric.aggregate_status} {workflow_metric.completed_at_ns}" + ) else: raise ValueError( f"Unsupported object type {type(workflow_metric)}: {str(workflow_metric)}" diff --git a/.ci/metrics/metrics_test.py b/.ci/metrics/metrics_test.py index 259e55f817939..406b68ae4d4f1 100644 --- a/.ci/metrics/metrics_test.py +++ b/.ci/metrics/metrics_test.py @@ -36,7 +36,9 @@ def test_upload_gauge_metric(self): def test_upload_job_metric(self): """Test that we can upload a job metric correctly.""" test_metrics = [ - metrics.JobMetrics("test_job", 5, 10, 1, 1000, 7, "test_workflow") + metrics.JobMetrics( + "test_job", 5, 10, 1, 1000, 1000, 1000, 7, "test_workflow" + ) ] return_value = requests.Response() return_value.status_code = 204 @@ -49,6 +51,22 @@ def test_upload_job_metric(self): "test_job queue_time=5,run_time=10,status=1 1000", ) + def test_upload_aggregate_metric(self): + """Test that we can upload an aggregate metric correctly.""" + test_metrics = [ + metrics.AggregateMetric("stage1_aggregate", 211, 1124, 1, 1200, 9) + ] + return_value = requests.Response() + return_value.status_code = 204 + with unittest.mock.patch( + "requests.post", return_value=return_value + ) as post_mock: + metrics.upload_metrics(test_metrics, "test_userid", "test_aoi_key") + self.assertEqual( + post_mock.call_args.kwargs["data"], + "stage1_aggregate queue_time=211,run_time=1124,status=1 1200", + ) + def test_upload_unknown_metric(self): """Test we report an error if we encounter an unknown metric type.""" @@ -70,6 +88,326 @@ def test_bad_response_code(self): with unittest.mock.patch("requests.post", return_value=return_value) as _: metrics.upload_metrics(test_metrics, "test_userid", "test_api_key") + def test_create_and_append_aggregate_metric_1_stage(self): + """Test the creation of a single AggregateMetric""" + test_metrics = [ + metrics.JobMetrics( + "libcxx_stage1_test1", + 8, + 388, + 1, + created_at_ns=1755697953000000000, + started_at_ns=1755697961000000000, + completed_at_ns=1755698349000000000, + workflow_id=3, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage1_test2", + 107, + 357, + 1, + created_at_ns=1755697953000000000, + started_at_ns=1755698060000000000, + completed_at_ns=1755698417000000000, + workflow_id=3, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage1_test3", + 8, + 824, + 1, + created_at_ns=1755697953000000000, + started_at_ns=1755697961000000000, + completed_at_ns=1755698785000000000, + workflow_id=3, + workflow_name="Build and Test libc++", + ), + ] + metrics.create_and_append_libcxx_aggregates(test_metrics) + self.assertEqual(len(test_metrics), 4) + self.assertTrue(isinstance(test_metrics[-1], metrics.AggregateMetric)) + aggregate = test_metrics[-1] + self.assertEqual( + aggregate.aggregate_name, "github_libcxx_premerge_checks_stage1_aggregate" + ) + self.assertEqual(aggregate.aggregate_queue_time, 107) + self.assertEqual(aggregate.aggregate_run_time, 824) + self.assertEqual(aggregate.aggregate_status, 1) + self.assertEqual(aggregate.completed_at_ns, 1755698785000000000) + self.assertEqual(aggregate.workflow_id, 3) + + def test_create_and_append_aggregate_metric_multiple_workflow_ids(self): + """Test creation of AggregateMetric for same stage with diff workflow ids.""" + test_metrics = [ + metrics.JobMetrics( + "libcxx_stage1_test1", + 8, + 388, + 1, + created_at_ns=1755697953000000000, + started_at_ns=1755697961000000000, + completed_at_ns=1755698349000000000, + workflow_id=3, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage1_test2", + 107, + 357, + 0, + created_at_ns=1755697953000000000, + started_at_ns=1755698060000000000, + completed_at_ns=1755698417000000000, + workflow_id=3, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage1_test3", + 8, + 824, + 1, + created_at_ns=1755697953000000000, + started_at_ns=1755697961000000000, + completed_at_ns=1755698785000000000, + workflow_id=25, + workflow_name="Build and Test libc++", + ), + ] + metrics.create_and_append_libcxx_aggregates(test_metrics) + self.assertEqual(len(test_metrics), 5) + self.assertTrue(isinstance(test_metrics[3], metrics.AggregateMetric)) + self.assertTrue(isinstance(test_metrics[4], metrics.AggregateMetric)) + aggregate = test_metrics[3] + self.assertEqual( + aggregate.aggregate_name, "github_libcxx_premerge_checks_stage1_aggregate" + ) + self.assertEqual(aggregate.aggregate_queue_time, 107) + self.assertEqual(aggregate.aggregate_run_time, 456) + self.assertEqual(aggregate.aggregate_status, 0) + self.assertEqual(aggregate.completed_at_ns, 1755698417000000000) + self.assertEqual(aggregate.workflow_id, 3) + + aggregate = test_metrics[4] + self.assertEqual( + aggregate.aggregate_name, "github_libcxx_premerge_checks_stage1_aggregate" + ) + self.assertEqual(aggregate.aggregate_queue_time, 8) + self.assertEqual(aggregate.aggregate_run_time, 824) + self.assertEqual(aggregate.aggregate_status, 1) + self.assertEqual(aggregate.completed_at_ns, 1755698785000000000) + self.assertEqual(aggregate.workflow_id, 25) + + def test_create_and_append_aggregate_metric_3_stages(self): + """Test the creation of AggregateMetric for each of 3 stages.""" + test_metrics = [ + metrics.JobMetrics( + "libcxx_stage1_test1", + 124, + 1454, + 1, + created_at_ns=1755696929000000000, + started_at_ns=1755697053000000000, + completed_at_ns=1755698507000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage1_test2", + 129, + 827, + 1, + created_at_ns=1755696929000000000, + started_at_ns=1755697058000000000, + completed_at_ns=1755697885000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage2_test1", + 6, + 580, + 1, + created_at_ns=1755698507000000000, + started_at_ns=1755698513000000000, + completed_at_ns=1755699093000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage2_test2", + 7, + 473, + 1, + created_at_ns=1755698507000000000, + started_at_ns=1755698514000000000, + completed_at_ns=1755698987000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage2_test3", + 7, + 820, + 1, + created_at_ns=1755698507000000000, + started_at_ns=1755698514000000000, + completed_at_ns=1755699334000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage3_test1", + 7, + 919, + 1, + created_at_ns=1755709005000000000, + started_at_ns=1755709012000000000, + completed_at_ns=1755709931000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage3_test2", + 141, + 834, + 1, + created_at_ns=1755709005000000000, + started_at_ns=1755709146000000000, + completed_at_ns=1755709980000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "libcxx_stage3_test3", + 131, + 370, + 1, + created_at_ns=1755709005000000000, + started_at_ns=1755709136000000000, + completed_at_ns=1755709514000000000, + workflow_id=17, + workflow_name="Build and Test libc++", + ), + ] + metrics.create_and_append_libcxx_aggregates(test_metrics) + self.assertEqual(len(test_metrics), 11) + self.assertTrue(isinstance(test_metrics[8], metrics.AggregateMetric)) + self.assertTrue(isinstance(test_metrics[9], metrics.AggregateMetric)) + self.assertTrue(isinstance(test_metrics[10], metrics.AggregateMetric)) + aggregate = test_metrics[8] + self.assertEqual( + aggregate.aggregate_name, "github_libcxx_premerge_checks_stage1_aggregate" + ) + self.assertEqual(aggregate.aggregate_queue_time, 129) + self.assertEqual(aggregate.aggregate_run_time, 1454) + self.assertEqual(aggregate.aggregate_status, 1) + self.assertEqual(aggregate.completed_at_ns, 1755698507000000000) + self.assertEqual(aggregate.workflow_id, 17) + + aggregate = test_metrics[9] + self.assertEqual( + aggregate.aggregate_name, "github_libcxx_premerge_checks_stage2_aggregate" + ) + self.assertEqual(aggregate.aggregate_queue_time, 7) + self.assertEqual(aggregate.aggregate_run_time, 821) + self.assertEqual(aggregate.aggregate_status, 1) + self.assertEqual(aggregate.completed_at_ns, 1755699334000000000) + self.assertEqual(aggregate.workflow_id, 17) + + aggregate = test_metrics[10] + self.assertEqual( + aggregate.aggregate_name, "github_libcxx_premerge_checks_stage3_aggregate" + ) + self.assertEqual(aggregate.aggregate_queue_time, 141) + self.assertEqual(aggregate.aggregate_run_time, 968) + self.assertEqual(aggregate.aggregate_status, 1) + self.assertEqual(aggregate.completed_at_ns, 1755709980000000000) + self.assertEqual(aggregate.workflow_id, 17) + + def test_create_and_append_aggregate_metric_mixed_job_types(self): + """Test the creation of AggregateMetric with non-lib++ jobs thrown in.""" + test_metrics = [ + metrics.JobMetrics( + "ci_test1", 5, 10, 1, 1000, 1200, 1400, 5, "premerge_test" + ), + metrics.JobMetrics( + "libcxx_stage1_test1", + 8, + 388, + 1, + created_at_ns=1755697953000000000, + started_at_ns=1755697961000000000, + completed_at_ns=1755698349000000000, + workflow_id=3, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "ci_test2", 3, 20, 1, 2000, 2200, 2400, 37, "premerge_test" + ), + metrics.JobMetrics( + "libcxx_stage1_test2", + 107, + 357, + 0, + created_at_ns=1755697953000000000, + started_at_ns=1755698060000000000, + completed_at_ns=1755698417000000000, + workflow_id=3, + workflow_name="Build and Test libc++", + ), + metrics.JobMetrics( + "ci_test3", 7, 35, 1, 3000, 3200, 3400, 85, "premerge_test" + ), + ] + metrics.create_and_append_libcxx_aggregates(test_metrics) + self.assertEqual(len(test_metrics), 6) + self.assertTrue(isinstance(test_metrics[5], metrics.AggregateMetric)) + aggregate = test_metrics[5] + self.assertEqual( + aggregate.aggregate_name, "github_libcxx_premerge_checks_stage1_aggregate" + ) + self.assertEqual(aggregate.aggregate_queue_time, 107) + self.assertEqual(aggregate.aggregate_run_time, 456) + self.assertEqual(aggregate.aggregate_status, 0) + self.assertEqual(aggregate.completed_at_ns, 1755698417000000000) + self.assertEqual(aggregate.workflow_id, 3) + + def test_create_and_append_aggregate_metric_no_libcxx_jobs(self): + """Test the creation of AggregateMetric with no libc++ jobs. + + In this case, no AggregateMetric should be created, but no + errors or complaints should be raised. + """ + test_metrics = [ + metrics.JobMetrics( + "ci_test1", 5, 10, 1, 1000, 1200, 1400, 5, "premerge_test" + ), + metrics.JobMetrics( + "ci_test2", 3, 20, 1, 2000, 2200, 2400, 37, "premerge_test" + ), + metrics.JobMetrics( + "ci_test3", 7, 35, 1, 3000, 3200, 3400, 85, "premerge_test" + ), + ] + metrics.create_and_append_libcxx_aggregates(test_metrics) + self.assertEqual(len(test_metrics), 3) + + def test_clean_up_libcxx_job_name(self): + """Test that we correctly update (or not) libcxx job names.""" + stage1_name = "stage1 (test1, C++-test2, my-c++-test-25)" + stage2_name = "stage2 (generic-cxx26, clang-21, clang++21)" + stage3_name = "stage3 (generic-cxx26, libcxx-next-runners, junk)" + bad_name = "this is a bad name" + out_name1 = metrics.clean_up_libcxx_job_name(stage1_name) + self.assertEqual(out_name1, "stage1_test1__Cxx_test2__my_cxx_test_25") + out_name2 = metrics.clean_up_libcxx_job_name(stage2_name) + self.assertEqual(out_name2, "stage2_generic_cxx26__clang_21__clangxx21") + out_name3 = metrics.clean_up_libcxx_job_name(stage3_name) + self.assertEqual(out_name3, "stage3_generic_cxx26__libcxx_next_runners__junk") + out_name4 = metrics.clean_up_libcxx_job_name(bad_name) + self.assertEqual(out_name4, bad_name) if __name__ == "__main__": unittest.main() diff --git a/.ci/monolithic-linux.sh b/.ci/monolithic-linux.sh index 6db24d894eb73..3ca4d05f4c891 100755 --- a/.ci/monolithic-linux.sh +++ b/.ci/monolithic-linux.sh @@ -13,36 +13,15 @@ # run only the relevant tests. # -set -ex -set -o pipefail +source .ci/utils.sh -MONOREPO_ROOT="${MONOREPO_ROOT:="$(git rev-parse --show-toplevel)"}" -BUILD_DIR="${BUILD_DIR:=${MONOREPO_ROOT}/build}" INSTALL_DIR="${BUILD_DIR}/install" -rm -rf "${BUILD_DIR}" - -sccache --zero-stats mkdir -p artifacts/reproducers -# Make sure any clang reproducers will end up as artifacts. +# Make sure any clang reproducers will end up as artifacts export CLANG_CRASH_DIAGNOSTICS_DIR=`realpath artifacts/reproducers` -function at-exit { - retcode=$? - - sccache --show-stats > artifacts/sccache_stats.txt - cp "${BUILD_DIR}"/.ninja_log artifacts/.ninja_log - cp "${BUILD_DIR}"/test-results.*.xml artifacts/ || : - - # If building fails there will be no results files. - shopt -s nullglob - - python3 "${MONOREPO_ROOT}"/.ci/generate_test_report_github.py ":penguin: Linux x64 Test Results" \ - $retcode "${BUILD_DIR}"/test-results.*.xml >> $GITHUB_STEP_SUMMARY -} -trap at-exit EXIT - projects="${1}" targets="${2}" runtimes="${3}" @@ -50,9 +29,9 @@ runtime_targets="${4}" runtime_targets_needs_reconfig="${5}" enable_cir="${6}" -lit_args="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --use-unique-output-file-name --timeout=1200 --time-tests" +lit_args="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --use-unique-output-file-name --timeout=1200 --time-tests --succinct" -echo "::group::cmake" +start-group "CMake" export PIP_BREAK_SYSTEM_PACKAGES=1 pip install -q -r "${MONOREPO_ROOT}"/.ci/all_requirements.txt @@ -81,51 +60,44 @@ cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \ -D MLIR_ENABLE_BINDINGS_PYTHON=ON \ -D LLDB_ENABLE_PYTHON=ON \ -D LLDB_ENFORCE_STRICT_TEST_REQUIREMENTS=ON \ - -D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" + -D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ + -D CMAKE_EXE_LINKER_FLAGS="-no-pie" -echo "::endgroup::" -echo "::group::ninja" +start-group "ninja" # Targets are not escaped as they are passed as separate arguments. -ninja -C "${BUILD_DIR}" -k 0 ${targets} - -echo "::endgroup::" +ninja -C "${BUILD_DIR}" -k 0 ${targets} |& tee ninja.log if [[ "${runtime_targets}" != "" ]]; then - echo "::group::ninja runtimes" - - ninja -C "${BUILD_DIR}" ${runtime_targets} + start-group "ninja Runtimes" - echo "::endgroup::" + ninja -C "${BUILD_DIR}" ${runtime_targets} |& tee ninja_runtimes.log fi # Compiling runtimes with just-built Clang and running their tests # as an additional testing for Clang. if [[ "${runtime_targets_needs_reconfig}" != "" ]]; then - echo "::group::cmake runtimes C++26" + start-group "CMake Runtimes C++26" cmake \ -D LIBCXX_TEST_PARAMS="std=c++26" \ -D LIBCXXABI_TEST_PARAMS="std=c++26" \ "${BUILD_DIR}" - echo "::endgroup::" - echo "::group::ninja runtimes C++26" + start-group "ninja Runtimes C++26" - ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} + ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} \ + |& tee ninja_runtimes_needs_reconfig1.log - echo "::endgroup::" - echo "::group::cmake runtimes clang modules" + start-group "CMake Runtimes Clang Modules" cmake \ -D LIBCXX_TEST_PARAMS="enable_modules=clang" \ -D LIBCXXABI_TEST_PARAMS="enable_modules=clang" \ "${BUILD_DIR}" - echo "::endgroup::" - echo "::group::ninja runtimes clang modules" - - ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} + start-group "ninja Runtimes Clang Modules" - echo "::endgroup::" + ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} \ + |& tee ninja_runtimes_needs_reconfig2.log fi diff --git a/.ci/monolithic-windows.sh b/.ci/monolithic-windows.sh index 50a741677d734..0f3a1994a0497 100755 --- a/.ci/monolithic-windows.sh +++ b/.ci/monolithic-windows.sh @@ -13,35 +13,12 @@ # run only the relevant tests. # -set -ex -set -o pipefail - -MONOREPO_ROOT="${MONOREPO_ROOT:="$(git rev-parse --show-toplevel)"}" -BUILD_DIR="${BUILD_DIR:=${MONOREPO_ROOT}/build}" - -rm -rf "${BUILD_DIR}" - -sccache --zero-stats -function at-exit { - retcode=$? - - mkdir -p artifacts - sccache --show-stats >> artifacts/sccache_stats.txt - cp "${BUILD_DIR}"/.ninja_log artifacts/.ninja_log - cp "${BUILD_DIR}"/test-results.*.xml artifacts/ || : - - # If building fails there will be no results files. - shopt -s nullglob - - python "${MONOREPO_ROOT}"/.ci/generate_test_report_github.py ":window: Windows x64 Test Results" \ - $retcode "${BUILD_DIR}"/test-results.*.xml >> $GITHUB_STEP_SUMMARY -} -trap at-exit EXIT +source .ci/utils.sh projects="${1}" targets="${2}" -echo "::group::cmake" +start-group "CMake" pip install -q -r "${MONOREPO_ROOT}"/.ci/all_requirements.txt export CC=cl @@ -62,7 +39,7 @@ cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \ -D LLVM_ENABLE_ASSERTIONS=ON \ -D LLVM_BUILD_EXAMPLES=ON \ -D COMPILER_RT_BUILD_LIBFUZZER=OFF \ - -D LLVM_LIT_ARGS="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --use-unique-output-file-name --timeout=1200 --time-tests" \ + -D LLVM_LIT_ARGS="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --use-unique-output-file-name --timeout=1200 --time-tests --succinct" \ -D COMPILER_RT_BUILD_ORC=OFF \ -D CMAKE_C_COMPILER_LAUNCHER=sccache \ -D CMAKE_CXX_COMPILER_LAUNCHER=sccache \ @@ -71,10 +48,7 @@ cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \ -D CMAKE_MODULE_LINKER_FLAGS="/MANIFEST:NO" \ -D CMAKE_SHARED_LINKER_FLAGS="/MANIFEST:NO" -echo "::endgroup::" -echo "::group::ninja" +start-group "ninja" # Targets are not escaped as they are passed as separate arguments. -ninja -C "${BUILD_DIR}" -k 0 ${targets} - -echo "::endgroup" +ninja -C "${BUILD_DIR}" -k 0 ${targets} |& tee ninja.log diff --git a/.ci/utils.sh b/.ci/utils.sh new file mode 100644 index 0000000000000..2a3d2426b630a --- /dev/null +++ b/.ci/utils.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +#===----------------------------------------------------------------------===## +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===----------------------------------------------------------------------===## + +# This script performs some setup and contains some utilities used for in the +# monolithic-linux.sh and monolithic-windows.sh scripts. + +set -ex +set -o pipefail + +MONOREPO_ROOT="${MONOREPO_ROOT:="$(git rev-parse --show-toplevel)"}" +BUILD_DIR="${BUILD_DIR:=${MONOREPO_ROOT}/build}" + +rm -rf "${BUILD_DIR}" + +sccache --zero-stats + +function at-exit { + retcode=$? + + mkdir -p artifacts + sccache --show-stats + sccache --show-stats >> artifacts/sccache_stats.txt + cp "${BUILD_DIR}"/.ninja_log artifacts/.ninja_log + cp "${MONOREPO_ROOT}"/*.log artifacts/ || : + cp "${BUILD_DIR}"/test-results.*.xml artifacts/ || : + + # If building fails there will be no results files. + shopt -s nullglob + + if [[ "$GITHUB_STEP_SUMMARY" != "" ]]; then + python "${MONOREPO_ROOT}"/.ci/generate_test_report_github.py \ + $retcode "${BUILD_DIR}"/test-results.*.xml "${MONOREPO_ROOT}"/ninja*.log \ + >> $GITHUB_STEP_SUMMARY + fi +} +trap at-exit EXIT + +function start-group { + groupname=$1 + if [[ "$GITHUB_ACTIONS" != "" ]]; then + echo "::endgroup" + echo "::group::$groupname" + elif [[ "$POSTCOMMIT_CI" != "" ]]; then + echo "@@@$STEP@@@" + else + echo "Starting $groupname" + fi +} diff --git a/.github/new-prs-labeler.yml b/.github/new-prs-labeler.yml index 8e0fa8d42d735..dab3db2616f59 100644 --- a/.github/new-prs-labeler.yml +++ b/.github/new-prs-labeler.yml @@ -90,9 +90,6 @@ LTO: - llvm/lib/Transforms/*/FunctionImport* - llvm/tools/gold/** -mc: - - llvm/*/MC/** - clang:driver: - clang/*/Driver/** @@ -621,6 +618,12 @@ llvm:adt: llvm:support: - llvm/**/Support/** +# Skip llvm/test/MC and llvm/unittests/MC, which includes target-specific directories. +llvm:mc: + - llvm/include/llvm/MC/** + - llvm/lib/MC/** + - llvm/tools/llvm-mc/** + llvm:transforms: - llvm/lib/Transforms/** - llvm/include/llvm/Transforms/** diff --git a/.github/workflows/bazel-checks.yml b/.github/workflows/bazel-checks.yml new file mode 100644 index 0000000000000..65d51649dd9e7 --- /dev/null +++ b/.github/workflows/bazel-checks.yml @@ -0,0 +1,32 @@ +name: Bazel Checks + +permissions: + contents: read + +on: + push: + paths: + - '.github/workflows/bazel-checks.yml' + - 'utils/bazel/**' + branches: + - main + pull_request: + paths: + - '.github/workflows/bazel-checks.yml' + - 'utils/bazel/**' + +jobs: + buildifier: + name: "Buildifier" + runs-on: ubuntu-24.04 + if: github.repository == 'llvm/llvm-project' + steps: + - name: Fetch LLVM sources + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Setup Buildifier + run: | + sudo curl -L https://github.com/bazelbuild/buildtools/releases/download/v8.2.1/buildifier-linux-amd64 -o /usr/bin/buildifier + sudo chmod +x /usr/bin/buildifier + - name: Run Buildifier + run: | + buildifier --mode=check $(find ./utils/bazel -name *BUILD*) diff --git a/.github/workflows/build-ci-container-windows.yml b/.github/workflows/build-ci-container-windows.yml index f76c69f29fb30..55a269c001c2b 100644 --- a/.github/workflows/build-ci-container-windows.yml +++ b/.github/workflows/build-ci-container-windows.yml @@ -25,7 +25,7 @@ jobs: container-filename: ${{ steps.vars.outputs.container-filename }} steps: - name: Checkout LLVM - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: .github/workflows/containers/github-action-ci-windows - name: Write Variables diff --git a/.github/workflows/build-ci-container.yml b/.github/workflows/build-ci-container.yml index 7f01264af8534..3e91c49a51d19 100644 --- a/.github/workflows/build-ci-container.yml +++ b/.github/workflows/build-ci-container.yml @@ -30,7 +30,7 @@ jobs: runs-on: depot-ubuntu-24.04-arm-16 steps: - name: Checkout LLVM - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: .github/workflows/containers/github-action-ci/ # podman is not installed by default on the ARM64 images. diff --git a/.github/workflows/build-metrics-container.yml b/.github/workflows/build-metrics-container.yml index af4d599f76417..265fd73cc0bb7 100644 --- a/.github/workflows/build-metrics-container.yml +++ b/.github/workflows/build-metrics-container.yml @@ -27,7 +27,7 @@ jobs: container-filename: ${{ steps.vars.outputs.container-filename }} steps: - name: Checkout LLVM - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: .ci/metrics/ - name: Write Variables diff --git a/.github/workflows/check-ci.yml b/.github/workflows/check-ci.yml index befea2093f908..7e8c15696e344 100644 --- a/.github/workflows/check-ci.yml +++ b/.github/workflows/check-ci.yml @@ -5,6 +5,8 @@ permissions: on: push: + branches: + - main paths: - '.ci/**' - '.github/workflows/check-ci.yml' @@ -20,7 +22,7 @@ jobs: if: github.repository == 'llvm/llvm-project' steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: .ci - name: Setup Python diff --git a/.github/workflows/ci-post-commit-analyzer.yml b/.github/workflows/ci-post-commit-analyzer.yml index b8074859d23a2..7d37b900d7909 100644 --- a/.github/workflows/ci-post-commit-analyzer.yml +++ b/.github/workflows/ci-post-commit-analyzer.yml @@ -41,7 +41,7 @@ jobs: LLVM_VERSION: 18 steps: - name: Checkout Source - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup ccache uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17 diff --git a/.github/workflows/commit-access-greeter.yml b/.github/workflows/commit-access-greeter.yml index a5fbbbb94e222..f31cd015642e2 100644 --- a/.github/workflows/commit-access-greeter.yml +++ b/.github/workflows/commit-access-greeter.yml @@ -18,7 +18,7 @@ jobs: github.event.label.name == 'infra:commit-access-request' runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: llvm/utils/git/ diff --git a/.github/workflows/commit-access-review.yml b/.github/workflows/commit-access-review.yml index d401a137737c4..a7be81b0e2da5 100644 --- a/.github/workflows/commit-access-review.yml +++ b/.github/workflows/commit-access-review.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install dependencies run: | diff --git a/.github/workflows/containers/github-action-ci-windows/Dockerfile b/.github/workflows/containers/github-action-ci-windows/Dockerfile index c06fcc0580756..640d34da02532 100644 --- a/.github/workflows/containers/github-action-ci-windows/Dockerfile +++ b/.github/workflows/containers/github-action-ci-windows/Dockerfile @@ -90,7 +90,7 @@ RUN powershell -Command \ RUN git config --system core.longpaths true & \ git config --global core.autocrlf false -ARG RUNNER_VERSION=2.327.1 +ARG RUNNER_VERSION=2.328.0 ENV RUNNER_VERSION=$RUNNER_VERSION RUN powershell -Command \ diff --git a/.github/workflows/containers/github-action-ci/Dockerfile b/.github/workflows/containers/github-action-ci/Dockerfile index 0444f617e3711..8a888f3a411c0 100644 --- a/.github/workflows/containers/github-action-ci/Dockerfile +++ b/.github/workflows/containers/github-action-ci/Dockerfile @@ -59,9 +59,12 @@ RUN apt-get update && \ sudo \ # These are needed by the premerge pipeline. Pip is used to install # dependent python packages. File and tzdata are used for tests. + # Having a symlink from python to python3 enables code sharing between + # the Linux and Windows pipelines. python3-pip \ file \ - tzdata && \ + tzdata \ + python-is-python3 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -78,6 +81,8 @@ RUN curl -L 'https://github.com/mozilla/sccache/releases/download/v0.10.0/sccach ENV LLVM_SYSROOT=$LLVM_SYSROOT ENV PATH=${LLVM_SYSROOT}/bin:${PATH} +ENV CC=clang +ENV CXX=clang++ # Create a new user to avoid test failures related to a lack of expected # permissions issues in some tests. Set the user id to 1001 as that is the @@ -94,7 +99,7 @@ WORKDIR /home/gha FROM ci-container as ci-container-agent -ENV GITHUB_RUNNER_VERSION=2.327.1 +ENV GITHUB_RUNNER_VERSION=2.328.0 RUN mkdir actions-runner && \ cd actions-runner && \ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3970271e4adbd..b627803f61b27 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -55,7 +55,7 @@ jobs: if: github.repository == 'llvm/llvm-project' steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 2 - name: Get subprojects that have doc changes diff --git a/.github/workflows/email-check.yaml b/.github/workflows/email-check.yaml index 904ad718f97dd..9390fba4d4e3b 100644 --- a/.github/workflows/email-check.yaml +++ b/.github/workflows/email-check.yaml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'llvm/llvm-project' steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/hlsl-test-all.yaml b/.github/workflows/hlsl-test-all.yaml index b6530fe11b840..72cbbe2b7dded 100644 --- a/.github/workflows/hlsl-test-all.yaml +++ b/.github/workflows/hlsl-test-all.yaml @@ -29,25 +29,25 @@ jobs: runs-on: ${{ inputs.SKU }} steps: - name: Checkout DXC - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: Microsoft/DirectXShaderCompiler ref: main path: DXC submodules: true - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ inputs.LLVM-branch }} path: llvm-project - name: Checkout OffloadTest - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: llvm/offload-test-suite ref: main path: OffloadTest - name: Checkout Golden Images - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: llvm/offload-golden-images ref: main diff --git a/.github/workflows/issue-release-workflow.yml b/.github/workflows/issue-release-workflow.yml index efd045990d013..7fd0280b2eedf 100644 --- a/.github/workflows/issue-release-workflow.yml +++ b/.github/workflows/issue-release-workflow.yml @@ -42,7 +42,7 @@ jobs: contains(github.event.action == 'opened' && github.event.issue.body || github.event.comment.body, '/cherry-pick') steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: llvm/llvm-project # GitHub stores the token used for checkout and uses it for pushes diff --git a/.github/workflows/issue-subscriber.yml b/.github/workflows/issue-subscriber.yml index de1c45c944960..afcd17c757b39 100644 --- a/.github/workflows/issue-subscriber.yml +++ b/.github/workflows/issue-subscriber.yml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'llvm/llvm-project' steps: - name: Checkout Automation Script - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: llvm/utils/git/ ref: main diff --git a/.github/workflows/issue-write.yml b/.github/workflows/issue-write.yml index a2c4f58d6febe..3036582a64a58 100644 --- a/.github/workflows/issue-write.yml +++ b/.github/workflows/issue-write.yml @@ -25,7 +25,7 @@ jobs: ) steps: - name: Fetch Sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: | .github/workflows/unprivileged-download-artifact/action.yml diff --git a/.github/workflows/libc-fullbuild-tests.yml b/.github/workflows/libc-fullbuild-tests.yml index 9ba77ff8bb845..8967cd0949c11 100644 --- a/.github/workflows/libc-fullbuild-tests.yml +++ b/.github/workflows/libc-fullbuild-tests.yml @@ -52,7 +52,7 @@ jobs: # - c_compiler: gcc # cpp_compiler: g++ steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Libc's build is relatively small comparing with other components of LLVM. # A fresh fullbuild takes about 190MiB of uncompressed disk space, which can diff --git a/.github/workflows/libc-overlay-tests.yml b/.github/workflows/libc-overlay-tests.yml index e3dc4166aa26c..7154946ac5c3d 100644 --- a/.github/workflows/libc-overlay-tests.yml +++ b/.github/workflows/libc-overlay-tests.yml @@ -41,7 +41,7 @@ jobs: cpp_compiler: clang++ steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Libc's build is relatively small comparing with other components of LLVM. # A fresh linux overlay takes about 180MiB of uncompressed disk space, which can diff --git a/.github/workflows/libclang-abi-tests.yml b/.github/workflows/libclang-abi-tests.yml index 4d47c07f42205..3836cc56a7c22 100644 --- a/.github/workflows/libclang-abi-tests.yml +++ b/.github/workflows/libclang-abi-tests.yml @@ -38,7 +38,7 @@ jobs: LLVM_VERSION_PATCH: ${{ steps.version.outputs.patch }} steps: - name: Checkout source - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 250 diff --git a/.github/workflows/libclang-python-tests.yml b/.github/workflows/libclang-python-tests.yml index 50ef4acf2feb1..e168928325561 100644 --- a/.github/workflows/libclang-python-tests.yml +++ b/.github/workflows/libclang-python-tests.yml @@ -4,7 +4,6 @@ permissions: contents: read on: - workflow_dispatch: push: branches: - 'main' @@ -13,29 +12,46 @@ on: - 'clang/tools/libclang/**' - 'clang/CMakeList.txt' - '.github/workflows/libclang-python-tests.yml' - - '.github/workflows/llvm-project-tests.yml' pull_request: paths: - 'clang/bindings/python/**' - 'clang/tools/libclang/**' - 'clang/CMakeList.txt' - '.github/workflows/libclang-python-tests.yml' - - '.github/workflows/llvm-project-tests.yml' jobs: check-clang-python: # Build libclang and then run the libclang Python binding's unit tests. + # There is an issue running on "windows-2019". + # See https://github.com/llvm/llvm-project/issues/76601#issuecomment-1873049082. name: Build and run Python unit tests if: github.repository == 'llvm/llvm-project' + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: python-version: ["3.8", "3.13"] - uses: ./.github/workflows/llvm-project-tests.yml - with: - build_target: check-clang-python - projects: clang - # There is an issue running on "windows-2019". - # See https://github.com/llvm/llvm-project/issues/76601#issuecomment-1873049082. - os_list: '["ubuntu-24.04"]' - python_version: ${{ matrix.python-version }} + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Setup Python + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + with: + python-version: ${{ matrix.python-version }} + - name: Setup ccache + uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17 + with: + max-size: 2G + key: spirv-ubuntu-24.04 + variant: sccache + - name: Build and Test + run: | + mkdir build + cmake -GNinja \ + -S llvm \ + -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCMAKE_C_COMPILER_LAUNCHER=sccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache \ + -DLLVM_ENABLE_PROJECTS=clang + ninja -C build check-clang-python diff --git a/.github/workflows/libcxx-build-and-test.yaml b/.github/workflows/libcxx-build-and-test.yaml index 4b29c9296bf9d..2e6ff7f91b6fc 100644 --- a/.github/workflows/libcxx-build-and-test.yaml +++ b/.github/workflows/libcxx-build-and-test.yaml @@ -36,7 +36,7 @@ concurrency: jobs: stage1: if: github.repository_owner == 'llvm' - runs-on: llvm-premerge-libcxx-runners + runs-on: llvm-premerge-libcxx-next-runners continue-on-error: false strategy: fail-fast: false @@ -47,14 +47,14 @@ jobs: 'generic-cxx26', 'generic-modules' ] - cc: [ 'clang-21' ] - cxx: [ 'clang++-21' ] + cc: [ 'clang-22' ] + cxx: [ 'clang++-22' ] include: - config: 'generic-gcc' cc: 'gcc-15' cxx: 'g++-15' steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: ${{ matrix.config }}.${{ matrix.cxx }} run: libcxx/utils/ci/run-buildbot ${{ matrix.config }} env: @@ -73,7 +73,7 @@ jobs: **/crash_diagnostics/* stage2: if: github.repository_owner == 'llvm' - runs-on: llvm-premerge-libcxx-runners + runs-on: llvm-premerge-libcxx-next-runners needs: [ stage1 ] continue-on-error: false strategy: @@ -86,20 +86,20 @@ jobs: 'generic-cxx20', 'generic-cxx23' ] - cc: [ 'clang-21' ] - cxx: [ 'clang++-21' ] + cc: [ 'clang-22' ] + cxx: [ 'clang++-22' ] include: - config: 'generic-gcc-cxx11' cc: 'gcc-15' cxx: 'g++-15' + - config: 'generic-cxx26' + cc: 'clang-21' + cxx: 'clang++-21' - config: 'generic-cxx26' cc: 'clang-20' cxx: 'clang++-20' - - config: 'generic-cxx26' - cc: 'clang-19' - cxx: 'clang++-19' steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: ${{ matrix.config }} run: libcxx/utils/ci/run-buildbot ${{ matrix.config }} env: @@ -148,27 +148,27 @@ jobs: 'generic-static', 'bootstrapping-build' ] - machine: [ 'llvm-premerge-libcxx-runners' ] + machine: [ 'llvm-premerge-libcxx-next-runners' ] include: - config: 'generic-cxx26' - machine: llvm-premerge-libcxx-runners + machine: llvm-premerge-libcxx-next-runners - config: 'generic-asan' - machine: llvm-premerge-libcxx-runners + machine: llvm-premerge-libcxx-next-runners - config: 'generic-tsan' - machine: llvm-premerge-libcxx-runners + machine: llvm-premerge-libcxx-next-runners - config: 'generic-ubsan' - machine: llvm-premerge-libcxx-runners + machine: llvm-premerge-libcxx-next-runners # Use a larger machine for MSAN to avoid timeout and memory allocation issues. - config: 'generic-msan' - machine: llvm-premerge-libcxx-runners + machine: llvm-premerge-libcxx-next-runners runs-on: ${{ matrix.machine }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: ${{ matrix.config }} run: libcxx/utils/ci/run-buildbot ${{ matrix.config }} env: - CC: clang-21 - CXX: clang++-21 + CC: clang-22 + CXX: clang++-22 - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: @@ -211,7 +211,7 @@ jobs: os: macos-15 runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 with: # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md @@ -252,7 +252,7 @@ jobs: - { config: mingw-dll-i686, mingw: true } - { config: mingw-incomplete-sysroot, mingw: true } steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install dependencies run: | choco install -y ninja diff --git a/.github/workflows/libcxx-build-containers.yml b/.github/workflows/libcxx-build-containers.yml index 43c446a2c02ce..c87ee8e3be250 100644 --- a/.github/workflows/libcxx-build-containers.yml +++ b/.github/workflows/libcxx-build-containers.yml @@ -30,7 +30,7 @@ jobs: packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Build the Linux builder image working-directory: libcxx/utils/ci diff --git a/.github/workflows/libcxx-check-generated-files.yml b/.github/workflows/libcxx-check-generated-files.yml index 0226edd7aa17a..f338bd6952779 100644 --- a/.github/workflows/libcxx-check-generated-files.yml +++ b/.github/workflows/libcxx-check-generated-files.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install dependencies uses: aminya/setup-cpp@17c11551771948abc5752bbf3183482567c7caf0 # v1.1.1 diff --git a/.github/workflows/llvm-project-tests.yml b/.github/workflows/llvm-project-tests.yml deleted file mode 100644 index d40ed5babb459..0000000000000 --- a/.github/workflows/llvm-project-tests.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: LLVM Project Tests - -permissions: - contents: read - -on: - workflow_dispatch: - inputs: - build_target: - required: false - projects: - required: false - extra_cmake_args: - required: false - os_list: - required: false - default: '["ubuntu-24.04", "windows-2019", "macOS-13"]' - python_version: - required: false - type: string - default: '3.11' - workflow_call: - inputs: - build_target: - required: false - type: string - default: "all" - - projects: - required: true - type: string - - extra_cmake_args: - required: false - type: string - - os_list: - required: false - type: string - # Use windows-2019 due to: - # https://developercommunity.visualstudio.com/t/Prev-Issue---with-__assume-isnan-/1597317 - default: '["ubuntu-24.04", "windows-2019", "macOS-13"]' - - python_version: - required: false - type: string - default: '3.11' - -concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - # If the group name here is the same as the group name in the workflow that includes - # this one, then the action will try to wait on itself and get stuck. - group: llvm-project-${{ github.workflow }}-${{ inputs.projects }}-${{ inputs.python_version }}${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} - -jobs: - lit-tests: - name: Lit Tests - runs-on: ${{ matrix.os }} - container: - image: ${{(startsWith(matrix.os, 'ubuntu') && 'ghcr.io/llvm/ci-ubuntu-24.04:latest') || null}} - volumes: - - /mnt/:/mnt/ - strategy: - fail-fast: false - matrix: - os: ${{ fromJSON(inputs.os_list) }} - steps: - - name: Setup Windows - if: startsWith(matrix.os, 'windows') - uses: llvm/actions/setup-windows@main - with: - arch: amd64 - # On Windows, starting with win19/20220814.1, cmake choose the 32-bit - # python3.10.6 libraries instead of the 64-bit libraries when building - # lldb. Using this setup-python action to make 3.10 the default - # python fixes this. - - name: Setup Python - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 - with: - python-version: ${{ inputs.python_version }} - - name: Install Ninja - if: runner.os != 'Linux' - uses: llvm/actions/install-ninja@main - # actions/checkout deletes any existing files in the new git directory, - # so this needs to either run before ccache-action or it has to use - # clean: false. - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 250 - - name: Setup ccache - uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17 - with: - # A full build of llvm, clang, lld, and lldb takes about 250MB - # of ccache space. There's not much reason to have more than this, - # because we usually won't need to save cache entries from older - # builds. Also, there is an overall 10GB cache limit, and each - # run creates a new cache entry so we want to ensure that we have - # enough cache space for all the tests to run at once and still - # fit under the 10 GB limit. - # Default to 2G to workaround: https://github.com/hendrikmuhs/ccache-action/issues/174 - max-size: 2G - key: ${{ matrix.os }} - variant: sccache - - name: Build and Test - env: - # Workaround for https://github.com/actions/virtual-environments/issues/5900. - # This should be a no-op for non-mac OSes - PKG_CONFIG_PATH: /usr/local/Homebrew/Library/Homebrew/os/mac/pkgconfig//12 - shell: bash - id: build-llvm - run: | - if [ "${{ runner.os }}" == "Linux" ]; then - builddir="/mnt/build/" - sudo mkdir -p $builddir - sudo chown gha $builddir - extra_cmake_args="-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang" - else - builddir="$(pwd)"/build - fi - if [ "${{ runner.os }}" == "macOS" ]; then - # Workaround test failure on some lld tests on MacOS - # https://github.com/llvm/llvm-project/issues/81967 - extra_cmake_args="-DLLVM_DISABLE_ASSEMBLY_FILES=ON" - fi - echo "llvm-builddir=$builddir" >> "$GITHUB_OUTPUT" - cmake -G Ninja \ - -B "$builddir" \ - -S llvm \ - -DLLVM_ENABLE_PROJECTS="${{ inputs.projects }}" \ - -DCMAKE_BUILD_TYPE=Release \ - -DLLVM_ENABLE_ASSERTIONS=ON \ - -DLLDB_INCLUDE_TESTS=OFF \ - -DLIBCLC_TARGETS_TO_BUILD="amdgcn--;amdgcn--amdhsa;r600--;nvptx--;nvptx64--;nvptx--nvidiacl;nvptx64--nvidiacl" \ - -DCMAKE_C_COMPILER_LAUNCHER=sccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=sccache \ - $extra_cmake_args \ - ${{ inputs.extra_cmake_args }} - ninja -C "$builddir" '${{ inputs.build_target }}' - - - name: Build and Test libclc - if: "!startsWith(matrix.os, 'windows') && contains(inputs.projects, 'libclc')" - env: - LLVM_BUILDDIR: ${{ steps.build-llvm.outputs.llvm-builddir }} - run: | - # The libclc tests don't have a generated check target so all we can - # do is build it. - ninja -C "$LLVM_BUILDDIR" diff --git a/.github/workflows/llvm-project-workflow-tests.yml b/.github/workflows/llvm-project-workflow-tests.yml deleted file mode 100644 index a2539b279be0a..0000000000000 --- a/.github/workflows/llvm-project-workflow-tests.yml +++ /dev/null @@ -1,32 +0,0 @@ -# This workflow will test the llvm-project-tests workflow in PRs -# targetting the main branch. Since this workflow doesn't normally -# run on main PRs, we need some way to test it to ensure new updates -# don't break it. - -name: LLVM Workflow Test - -permissions: - contents: read - -on: - pull_request: - branches: - - 'main' - paths: - - '.github/workflows/llvm-project-tests.yml' - - '.github/workflows/llvm-project-workflow-tests.yml' - -concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} - -jobs: - llvm-test: - if: github.repository_owner == 'llvm' - name: Build and Test - uses: ./.github/workflows/llvm-project-tests.yml - with: - build_target: check-all - projects: clang;lld;libclc;lldb diff --git a/.github/workflows/llvm-tests.yml b/.github/workflows/llvm-tests.yml index a9bd8db462cf7..52b486e7e62fc 100644 --- a/.github/workflows/llvm-tests.yml +++ b/.github/workflows/llvm-tests.yml @@ -38,7 +38,7 @@ jobs: LLVM_VERSION_PATCH: ${{ steps.version.outputs.patch }} steps: - name: Checkout source - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 250 diff --git a/.github/workflows/merged-prs.yml b/.github/workflows/merged-prs.yml index c771736389802..107bbc51b5314 100644 --- a/.github/workflows/merged-prs.yml +++ b/.github/workflows/merged-prs.yml @@ -21,7 +21,7 @@ jobs: (github.event.pull_request.merged == true) steps: - name: Checkout Automation Script - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: llvm/utils/git/ ref: main diff --git a/.github/workflows/mlir-spirv-tests.yml b/.github/workflows/mlir-spirv-tests.yml new file mode 100644 index 0000000000000..78952ccad2642 --- /dev/null +++ b/.github/workflows/mlir-spirv-tests.yml @@ -0,0 +1,51 @@ +name: MLIR SPIR-V Tests + +permissions: + contents: read + +on: + workflow_dispatch: + pull_request: + paths: + - 'mlir/include/mlir/Dialect/SPIRV/**' + - 'mlir/lib/Dialect/SPIRV/**' + - 'mlir/include/mlir/Target/SPIRV/**' + - 'mlir/lib/Target/SPIRV/**' + - 'mlir/test/Target/SPIRV/**' + - '.github/workflows/mlir-spirv-tests.yml' + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + +jobs: + check_spirv: + if: github.repository_owner == 'llvm' + name: Test MLIR SPIR-V + runs-on: ubuntu-24.04 + container: + image: ghcr.io/llvm/ci-ubuntu-24.04:latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Setup ccache + uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17 + with: + max-size: 2G + key: spirv-mlir-ubuntu-24.04 + variant: sccache + - name: Build and Test + run: | + mkdir build + cmake -GNinja \ + -S llvm \ + -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCMAKE_C_COMPILER_LAUNCHER=sccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache \ + -DLLVM_TARGETS_TO_BUILD="host" \ + -DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON \ + -DLLVM_ENABLE_PROJECTS=mlir + ninja -C build check-mlir diff --git a/.github/workflows/new-prs.yml b/.github/workflows/new-prs.yml index 935598e410dbb..e1f2e754c1a3d 100644 --- a/.github/workflows/new-prs.yml +++ b/.github/workflows/new-prs.yml @@ -35,7 +35,7 @@ jobs: (github.event.pull_request.author_association != 'OWNER') steps: - name: Checkout Automation Script - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: llvm/utils/git/ ref: main diff --git a/.github/workflows/pr-code-format.yml b/.github/workflows/pr-code-format.yml index 70bcaafbd0cf3..5540555ae05ed 100644 --- a/.github/workflows/pr-code-format.yml +++ b/.github/workflows/pr-code-format.yml @@ -19,7 +19,7 @@ jobs: if: github.repository == 'llvm/llvm-project' steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 2 @@ -35,7 +35,7 @@ jobs: # We need to pull the script from the main branch, so that we ensure # we get the latest version of this script. - name: Fetch code formatting utils - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: ${{ github.repository }} ref: ${{ github.base_ref }} @@ -70,8 +70,6 @@ jobs: - name: Run code formatter env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} - START_REV: ${{ github.event.pull_request.base.sha }} - END_REV: ${{ github.event.pull_request.head.sha }} CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} # Create an empty comments file so the pr-write job doesn't fail. run: | diff --git a/.github/workflows/pr-request-release-note.yml b/.github/workflows/pr-request-release-note.yml index 57425e04ec2f4..f0197d71d6aa9 100644 --- a/.github/workflows/pr-request-release-note.yml +++ b/.github/workflows/pr-request-release-note.yml @@ -19,7 +19,7 @@ jobs: # We need to pull the script from the main branch, so that we ensure # we get the latest version of this script. - name: Checkout Scripts - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: | llvm/utils/git/requirements.txt diff --git a/.github/workflows/pr-subscriber.yml b/.github/workflows/pr-subscriber.yml index f558da8a8fe0e..23c7a679185ee 100644 --- a/.github/workflows/pr-subscriber.yml +++ b/.github/workflows/pr-subscriber.yml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'llvm/llvm-project' steps: - name: Checkout Automation Script - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: llvm/utils/git/ ref: main diff --git a/.github/workflows/premerge.yaml b/.github/workflows/premerge.yaml index d0518fa6879e2..9d925517a7211 100644 --- a/.github/workflows/premerge.yaml +++ b/.github/workflows/premerge.yaml @@ -31,13 +31,10 @@ jobs: runs-on: llvm-premerge-linux-runners steps: - name: Checkout LLVM - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 2 - name: Build and Test - # Mark the job as a success even if the step fails so that people do - # not get notified while the new premerge pipeline is in an - # experimental state. run: | git config --global --add safe.directory '*' @@ -72,6 +69,11 @@ jobs: ./.ci/monolithic-linux.sh "${projects_to_build}" "${project_check_targets}" "${runtimes_to_build}" "${runtimes_check_targets}" "${runtimes_check_targets_needs_reconfig}" "${enable_cir}" - name: Upload Artifacts + # In some cases, Github will fail to upload the artifact. We want to + # continue anyways as a failed artifact upload is an infra failure, not + # a checks failure. + # https://github.com/actions/upload-artifact/issues/569 + continue-on-error: true if: '!cancelled()' uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: @@ -91,7 +93,7 @@ jobs: shell: bash steps: - name: Checkout LLVM - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 2 - name: Compute Projects @@ -109,9 +111,6 @@ jobs: echo "windows-projects=${projects_to_build}" >> $GITHUB_OUTPUT echo "windows-check-targets=${project_check_targets}" >> $GITHUB_OUTPUT - name: Build and Test - # Mark the job as a success even if the step fails so that people do - # not get notified while the new premerge pipeline is in an - # experimental state. if: ${{ steps.vars.outputs.windows-projects != '' }} shell: cmd run: | @@ -120,6 +119,11 @@ jobs: # these environment variables. bash -c "export SCCACHE_GCS_BUCKET=$CACHE_GCS_BUCKET; export SCCACHE_GCS_RW_MODE=READ_WRITE; export SCCACHE_IDLE_TIMEOUT=0; sccache --start-server; .ci/monolithic-windows.sh \"${{ steps.vars.outputs.windows-projects }}\" \"${{ steps.vars.outputs.windows-check-targets }}\"" - name: Upload Artifacts + # In some cases, Github will fail to upload the artifact. We want to + # continue anyways as a failed artifact upload is an infra failure, not + # a checks failure. + # https://github.com/actions/upload-artifact/issues/569 + continue-on-error: true if: '!cancelled()' uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: @@ -138,7 +142,7 @@ jobs: (github.event_name != 'pull_request' || github.event.action != 'closed') steps: - name: Checkout LLVM - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 2 - name: Setup ccache diff --git a/.github/workflows/release-asset-audit.yml b/.github/workflows/release-asset-audit.yml index 7a1f232ae7335..6546540a1b547 100644 --- a/.github/workflows/release-asset-audit.yml +++ b/.github/workflows/release-asset-audit.yml @@ -23,7 +23,7 @@ jobs: if: github.repository == 'llvm/llvm-project' steps: - name: Checkout LLVM - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: | .github/workflows/release-asset-audit.py diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index c113b42dc8ed4..116bdfb3929d3 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -73,7 +73,7 @@ jobs: python-version: '3.12' - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install Dependencies shell: bash @@ -195,7 +195,7 @@ jobs: steps: - name: Checkout Actions - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ (github.event_name == 'pull_request' && github.sha) || 'main' }} sparse-checkout: | @@ -216,7 +216,7 @@ jobs: run: mv workflows ../workflows-main - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ needs.prepare.outputs.ref }} @@ -286,7 +286,7 @@ jobs: steps: - name: Checkout Release Scripts - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: | llvm/utils/release/github-upload-release.py @@ -338,7 +338,7 @@ jobs: runs-on: ${{ needs.prepare.outputs.test-runs-on }} steps: - name: Checkout Actions - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ (github.event_name == 'pull_request' && github.sha) || 'main' }} sparse-checkout: | diff --git a/.github/workflows/release-documentation.yml b/.github/workflows/release-documentation.yml index 5a0aa063d32ac..712ff1831170e 100644 --- a/.github/workflows/release-documentation.yml +++ b/.github/workflows/release-documentation.yml @@ -34,7 +34,7 @@ jobs: upload: ${{ inputs.upload && !contains(inputs.release-version, 'rc') }} steps: - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Python env uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 @@ -66,7 +66,7 @@ jobs: - name: Clone www-releases if: env.upload - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: ${{ github.repository_owner }}/www-releases ref: main diff --git a/.github/workflows/release-doxygen.yml b/.github/workflows/release-doxygen.yml index d47c4337c07b2..17c677413f744 100644 --- a/.github/workflows/release-doxygen.yml +++ b/.github/workflows/release-doxygen.yml @@ -40,7 +40,7 @@ jobs: upload: ${{ inputs.upload && !contains(inputs.release-version, 'rc') }} steps: - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Python env uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 diff --git a/.github/workflows/release-lit.yml b/.github/workflows/release-lit.yml index 9adeffb74d52a..60ec64462bc31 100644 --- a/.github/workflows/release-lit.yml +++ b/.github/workflows/release-lit.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: "llvmorg-${{ inputs.release-version }}" diff --git a/.github/workflows/release-sources.yml b/.github/workflows/release-sources.yml index 99438918b56f0..14cc4c4e9b94f 100644 --- a/.github/workflows/release-sources.yml +++ b/.github/workflows/release-sources.yml @@ -71,7 +71,7 @@ jobs: attestations: write steps: - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ needs.inputs.outputs.ref }} fetch-tags: true diff --git a/.github/workflows/release-tasks.yml b/.github/workflows/release-tasks.yml index c9ae7e1ce97c3..a184996968cdd 100644 --- a/.github/workflows/release-tasks.yml +++ b/.github/workflows/release-tasks.yml @@ -38,7 +38,7 @@ jobs: sudo apt-get install python3-github - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Create Release env: @@ -129,7 +129,7 @@ jobs: sudo apt-get install python3-github - name: Checkout LLVM - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: sparse-checkout: llvm/utils/release/github-upload-release.py sparse-checkout-cone-mode: false diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 6cc80fb316c67..40db5504294ef 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -31,7 +31,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false diff --git a/.github/workflows/spirv-tests.yml b/.github/workflows/spirv-tests.yml index f15ca1cb64ba5..8708fb06d9eb8 100644 --- a/.github/workflows/spirv-tests.yml +++ b/.github/workflows/spirv-tests.yml @@ -4,7 +4,6 @@ permissions: contents: read on: - workflow_dispatch: pull_request: paths: - 'llvm/lib/Target/SPIRV/**' @@ -21,9 +20,27 @@ jobs: check_spirv: if: github.repository_owner == 'llvm' name: Test SPIR-V - uses: ./.github/workflows/llvm-project-tests.yml - with: - build_target: check-llvm-codegen-spirv - projects: - extra_cmake_args: '-DLLVM_TARGETS_TO_BUILD="SPIRV" -DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON' - os_list: '["ubuntu-24.04"]' + runs-on: ubuntu-24.04 + container: + image: ghcr.io/llvm/ci-ubuntu-24.04:latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Setup ccache + uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17 + with: + max-size: 2G + key: spirv-ubuntu-24.04 + variant: sccache + - name: Build and Test + run: | + mkdir build + cmake -GNinja \ + -S llvm \ + -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCMAKE_C_COMPILER_LAUNCHER=sccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache \ + -DLLVM_TARGETS_TO_BUILD="SPIRV" \ + -DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON + ninja -C build check-llvm-codegen-spirv diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index a0a598094376f..7e451880f4cfa 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Fetch LLVM sources - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 diff --git a/.gitignore b/.gitignore index a84268a7f6863..860b8ea12abd4 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,11 @@ autoconf/autom4te.cache # CLion project configuration /.idea /cmake-build* +# Coding assistants' stuff +/CLAUDE.md +/.claude/ +/GEMINI.md +/.gemini/ #==============================================================================# # Directories to ignore (do not add trailing '/'s, they skip symlinks). diff --git a/bolt/docs/CommandLineArgumentReference.md b/bolt/docs/CommandLineArgumentReference.md index f3881c9a640a9..d65cf39e16b29 100644 --- a/bolt/docs/CommandLineArgumentReference.md +++ b/bolt/docs/CommandLineArgumentReference.md @@ -138,6 +138,12 @@ Dump function CFGs to graphviz format after each stage;enable '-print-loops' for color-coded blocks +- `--dump-dot-func=` + + Dump function CFGs to graphviz format for specified functions only; + takes function name patterns (regex supported). Note: C++ function names + must be passed using their mangled names + - `--dump-linux-exceptions` Dump Linux kernel exception table diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h index ae580520b9110..b59926cc75571 100644 --- a/bolt/include/bolt/Core/BinaryFunction.h +++ b/bolt/include/bolt/Core/BinaryFunction.h @@ -1196,11 +1196,6 @@ class BinaryFunction { return getSecondaryEntryPointSymbol(BB.getLabel()); } - /// Remove a label from the secondary entry point map. - void removeSymbolFromSecondaryEntryPointMap(const MCSymbol *Label) { - SecondaryEntryPoints.erase(Label); - } - /// Return true if the basic block is an entry point into the function /// (either primary or secondary). bool isEntryPoint(const BinaryBasicBlock &BB) const { diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h index f902a8c43cd1d..ae04891e791f9 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -718,6 +718,20 @@ class MCPlusBuilder { return false; } + /// Returns true if Inst is a trap instruction. + /// + /// Tests if Inst is an instruction that immediately causes an abnormal + /// program termination, for example when a security violation is detected + /// by a compiler-inserted check. + /// + /// @note An implementation of this method should likely return false for + /// calls to library functions like abort(), as it is possible that the + /// execution state is partially attacker-controlled at this point. + virtual bool isTrap(const MCInst &Inst) const { + llvm_unreachable("not implemented"); + return false; + } + virtual bool isBreakpoint(const MCInst &Inst) const { llvm_unreachable("not implemented"); return false; @@ -740,6 +754,10 @@ class MCPlusBuilder { return false; } + /// Return true if the hlt instruction under the x86, otherwise, default to + /// false. + virtual bool isX86HLT(const MCInst &Inst) const { return false; } + /// Return the width, in bytes, of the memory access performed by \p Inst, if /// this is a pop instruction. Return zero otherwise. virtual int getPopSize(const MCInst &Inst) const { diff --git a/bolt/include/bolt/Profile/DataAggregator.h b/bolt/include/bolt/Profile/DataAggregator.h index cb1b87f8d0d65..db0f6903185b7 100644 --- a/bolt/include/bolt/Profile/DataAggregator.h +++ b/bolt/include/bolt/Profile/DataAggregator.h @@ -502,9 +502,6 @@ class DataAggregator : public DataReader { /// entries). void imputeFallThroughs(); - /// Register profiled functions for lite mode. - void registerProfiledFunctions(); - /// Debugging dump methods void dump() const; void dump(const PerfBranchSample &Sample) const; diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h index 91d62a78de390..19dcce8205ebc 100644 --- a/bolt/include/bolt/Rewrite/RewriteInstance.h +++ b/bolt/include/bolt/Rewrite/RewriteInstance.h @@ -241,7 +241,7 @@ class RewriteInstance { /// Adjust function sizes and set proper maximum size values after the whole /// symbol table has been processed. - void adjustFunctionBoundaries(); + void adjustFunctionBoundaries(DenseMap &MarkerSyms); /// Make .eh_frame section relocatable. void relocateEHFrameSection(); diff --git a/bolt/include/bolt/Utils/CommandLineOpts.h b/bolt/include/bolt/Utils/CommandLineOpts.h index a75b6bf720ec4..859d6f3bf6774 100644 --- a/bolt/include/bolt/Utils/CommandLineOpts.h +++ b/bolt/include/bolt/Utils/CommandLineOpts.h @@ -15,6 +15,12 @@ #include "llvm/Support/CommandLine.h" +namespace llvm { +namespace bolt { +class BinaryFunction; +} +} // namespace llvm + namespace opts { enum HeatmapModeKind { @@ -100,6 +106,9 @@ extern llvm::cl::opt Verbosity; /// Return true if we should process all functions in the binary. bool processAllFunctions(); +/// Return true if we should dump dot graphs for the given function. +bool shouldDumpDot(const llvm::bolt::BinaryFunction &Function); + enum GadgetScannerKind { GS_PACRET, GS_PAUTH, GS_ALL }; extern llvm::cl::bits GadgetScannersToRun; diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp index 84f1853469709..da59a188c6b60 100644 --- a/bolt/lib/Core/BinaryContext.cpp +++ b/bolt/lib/Core/BinaryContext.cpp @@ -2517,7 +2517,7 @@ BinaryContext::calculateEmittedSize(BinaryFunction &BF, bool FixBranches) { // Clean-up the effect of the code emission. for (const MCSymbol &Symbol : Assembler.symbols()) { MCSymbol *MutableSymbol = const_cast(&Symbol); - MutableSymbol->setUndefined(); + MutableSymbol->setFragment(nullptr); MutableSymbol->setIsRegistered(false); } diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp index eec68ff5a5fce..8f494f105fbba 100644 --- a/bolt/lib/Core/BinaryFunction.cpp +++ b/bolt/lib/Core/BinaryFunction.cpp @@ -1915,13 +1915,9 @@ void BinaryFunction::postProcessEntryPoints() { continue; // If we have grabbed a wrong code label which actually points to some - // constant island inside the function, ignore this label and remove it - // from the secondary entry point map. - if (isStartOfConstantIsland(Offset)) { - BC.SymbolToFunctionMap.erase(Label); - removeSymbolFromSecondaryEntryPointMap(Label); + // constant island inside the function, ignore this label. + if (isStartOfConstantIsland(Offset)) continue; - } BC.errs() << "BOLT-WARNING: reference in the middle of instruction " "detected in function " diff --git a/bolt/lib/Core/MCPlusBuilder.cpp b/bolt/lib/Core/MCPlusBuilder.cpp index fa8f4d1df308b..7f962e14ea115 100644 --- a/bolt/lib/Core/MCPlusBuilder.cpp +++ b/bolt/lib/Core/MCPlusBuilder.cpp @@ -30,6 +30,11 @@ using namespace bolt; using namespace MCPlus; namespace opts { +cl::opt + TerminalHLT("terminal-x86-hlt", + cl::desc("Assume that execution stops at x86 HLT instruction"), + cl::init(true), cl::Hidden, cl::cat(BoltCategory)); + cl::opt TerminalTrap("terminal-trap", cl::desc("Assume that execution stops at trap instruction"), @@ -132,8 +137,13 @@ bool MCPlusBuilder::equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B, } bool MCPlusBuilder::isTerminator(const MCInst &Inst) const { - return Analysis->isTerminator(Inst) || - (opts::TerminalTrap && Info->get(Inst.getOpcode()).isTrap()); + if (isX86HLT(Inst)) + return opts::TerminalHLT; + + if (Info->get(Inst.getOpcode()).isTrap()) + return opts::TerminalTrap; + + return Analysis->isTerminator(Inst); } void MCPlusBuilder::setTailCall(MCInst &Inst) const { diff --git a/bolt/lib/Passes/BinaryPasses.cpp b/bolt/lib/Passes/BinaryPasses.cpp index 5d44e1a1a4902..d7f02b9470030 100644 --- a/bolt/lib/Passes/BinaryPasses.cpp +++ b/bolt/lib/Passes/BinaryPasses.cpp @@ -662,7 +662,7 @@ Error CleanMCState::runOnFunctions(BinaryContext &BC) { if (S->isDefined()) { LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Symbol \"" << S->getName() << "\" is already defined\n"); - const_cast(S)->setUndefined(); + const_cast(S)->setFragment(nullptr); } if (S->isRegistered()) { LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Symbol \"" << S->getName() diff --git a/bolt/lib/Passes/FrameOptimizer.cpp b/bolt/lib/Passes/FrameOptimizer.cpp index 81d4d9367f58c..b0b7207feac01 100644 --- a/bolt/lib/Passes/FrameOptimizer.cpp +++ b/bolt/lib/Passes/FrameOptimizer.cpp @@ -224,6 +224,11 @@ Error FrameOptimizerPass::runOnFunctions(BinaryContext &BC) { if (opts::FrameOptimization == FOP_NONE) return Error::success(); + if (!BC.isX86()) { + BC.errs() << "BOLT-ERROR: " << getName() << " is supported only on X86\n"; + exit(1); + } + std::unique_ptr CG; std::unique_ptr FA; std::unique_ptr RA; diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp index f928dd49edb25..65c84ebc8c4f4 100644 --- a/bolt/lib/Passes/PAuthGadgetScanner.cpp +++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp @@ -1078,6 +1078,15 @@ class DstSafetyAnalysis { dbgs() << ")\n"; }); + // If this instruction terminates the program immediately, no + // authentication oracles are possible past this point. + if (BC.MIB->isTrap(Point)) { + LLVM_DEBUG({ traceInst(BC, "Trap instruction found", Point); }); + DstState Next(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters()); + Next.CannotEscapeUnchecked.set(); + return Next; + } + // If this instruction is reachable by the analysis, a non-empty state will // be propagated to it sooner or later. Until then, skip computeNext(). if (Cur.empty()) { @@ -1185,8 +1194,8 @@ class DataflowDstSafetyAnalysis // // A basic block without any successors, on the other hand, can be // pessimistically initialized to everything-is-unsafe: this will naturally - // handle both return and tail call instructions and is harmless for - // internal indirect branch instructions (such as computed gotos). + // handle return, trap and tail call instructions. At the same time, it is + // harmless for internal indirect branch instructions, like computed gotos. if (BB.succ_empty()) return createUnsafeState(); diff --git a/bolt/lib/Profile/DataAggregator.cpp b/bolt/lib/Profile/DataAggregator.cpp index c13fa6dbe582b..3604fdd3a94b4 100644 --- a/bolt/lib/Profile/DataAggregator.cpp +++ b/bolt/lib/Profile/DataAggregator.cpp @@ -581,26 +581,6 @@ void DataAggregator::imputeFallThroughs() { outs() << "BOLT-INFO: imputed " << InferredTraces << " traces\n"; } -void DataAggregator::registerProfiledFunctions() { - DenseSet Addrs; - for (const auto &Trace : llvm::make_first_range(Traces)) { - if (Trace.Branch != Trace::FT_ONLY && - Trace.Branch != Trace::FT_EXTERNAL_ORIGIN) - Addrs.insert(Trace.Branch); - Addrs.insert(Trace.From); - } - - for (const auto [PC, _] : BasicSamples) - Addrs.insert(PC); - - for (const PerfMemSample &MemSample : MemSamples) - Addrs.insert(MemSample.PC); - - for (const uint64_t Addr : Addrs) - if (BinaryFunction *Func = getBinaryFunctionContainingAddress(Addr)) - Func->setHasProfileAvailable(); -} - Error DataAggregator::preprocessProfile(BinaryContext &BC) { this->BC = &BC; @@ -623,7 +603,6 @@ Error DataAggregator::preprocessProfile(BinaryContext &BC) { exit(0); } - registerProfiledFunctions(); return Error::success(); } @@ -1368,6 +1347,10 @@ std::error_code DataAggregator::parseAggregatedLBREntry() { } const uint64_t FromOffset = Addr[0]->Offset; + BinaryFunction *FromFunc = getBinaryFunctionContainingAddress(FromOffset); + if (FromFunc) + FromFunc->setHasProfileAvailable(); + int64_t Count = Counters[0]; int64_t Mispreds = Counters[1]; @@ -1378,6 +1361,11 @@ std::error_code DataAggregator::parseAggregatedLBREntry() { return std::error_code(); } + const uint64_t ToOffset = Addr[1]->Offset; + BinaryFunction *ToFunc = getBinaryFunctionContainingAddress(ToOffset); + if (ToFunc) + ToFunc->setHasProfileAvailable(); + /// For fall-through types, adjust locations to match Trace container. if (Type == FT || Type == FT_EXTERNAL_ORIGIN || Type == FT_EXTERNAL_RETURN) { Addr[2] = Location(Addr[1]->Offset); // Trace To @@ -1625,6 +1613,9 @@ std::error_code DataAggregator::parseBranchEvents() { Traces.reserve(TraceMap.size()); for (const auto &[Trace, Info] : TraceMap) { Traces.emplace_back(Trace, Info); + for (const uint64_t Addr : {Trace.Branch, Trace.From}) + if (BinaryFunction *BF = getBinaryFunctionContainingAddress(Addr)) + BF->setHasProfileAvailable(); } clear(TraceMap); @@ -1685,6 +1676,9 @@ std::error_code DataAggregator::parseBasicEvents() { continue; ++NumTotalSamples; + if (BinaryFunction *BF = getBinaryFunctionContainingAddress(Sample->PC)) + BF->setHasProfileAvailable(); + ++BasicSamples[Sample->PC]; EventNames.insert(Sample->EventName); } @@ -1722,6 +1716,9 @@ std::error_code DataAggregator::parseMemEvents() { if (std::error_code EC = Sample.getError()) return EC; + if (BinaryFunction *BF = getBinaryFunctionContainingAddress(Sample->PC)) + BF->setHasProfileAvailable(); + MemSamples.emplace_back(std::move(Sample.get())); } diff --git a/bolt/lib/Rewrite/BinaryPassManager.cpp b/bolt/lib/Rewrite/BinaryPassManager.cpp index 996d2e972599d..0ddb73f828878 100644 --- a/bolt/lib/Rewrite/BinaryPassManager.cpp +++ b/bolt/lib/Rewrite/BinaryPassManager.cpp @@ -52,6 +52,7 @@ namespace opts { extern cl::opt PrintAll; extern cl::opt PrintDynoStats; extern cl::opt DumpDotAll; +extern bool shouldDumpDot(const bolt::BinaryFunction &Function); extern cl::opt AsmDump; extern cl::opt PLT; extern cl::opt KeepNops; extern cl::opt Lite; extern cl::list ReorderData; extern cl::opt ReorderFunctions; +extern cl::opt TerminalHLT; extern cl::opt TerminalTrap; extern cl::opt TimeBuild; extern cl::opt TimeRewrite; @@ -114,6 +115,35 @@ cl::opt DumpDotAll( "enable '-print-loops' for color-coded blocks"), cl::Hidden, cl::cat(BoltCategory)); +cl::list DumpDotFunc( + "dump-dot-func", cl::CommaSeparated, + cl::desc( + "dump function CFGs to graphviz format for specified functions only;" + "takes function name patterns (regex supported)"), + cl::value_desc("func1,func2,func3,..."), cl::Hidden, cl::cat(BoltCategory)); + +bool shouldDumpDot(const bolt::BinaryFunction &Function) { + // If dump-dot-all is enabled, dump all functions + if (DumpDotAll) + return !Function.isIgnored(); + + // If no specific functions specified in dump-dot-func, don't dump any + if (DumpDotFunc.empty()) + return false; + + if (Function.isIgnored()) + return false; + + // Check if function matches any of the specified patterns + for (const std::string &Name : DumpDotFunc) { + if (Function.hasNameRegex(Name)) { + return true; + } + } + + return false; +} + static cl::list ForceFunctionNames("funcs", cl::CommaSeparated, @@ -880,14 +910,9 @@ void RewriteInstance::discoverFileObjects() { // code section (see IHI0056B). $d identifies data contents. // Compilers usually merge multiple data objects in a single $d-$x interval, // but we need every data object to be marked with $d. Because of that we - // create a vector of MarkerSyms with all locations of data objects. + // keep track of marker symbols with all locations of data objects. - struct MarkerSym { - uint64_t Address; - MarkerSymType Type; - }; - - std::vector SortedMarkerSymbols; + DenseMap MarkerSymbols; auto addExtraDataMarkerPerSymbol = [&]() { bool IsData = false; uint64_t LastAddr = 0; @@ -911,14 +936,14 @@ void RewriteInstance::discoverFileObjects() { } if (MarkerType != MarkerSymType::NONE) { - SortedMarkerSymbols.push_back(MarkerSym{SymInfo.Address, MarkerType}); + MarkerSymbols[SymInfo.Address] = MarkerType; LastAddr = SymInfo.Address; IsData = MarkerType == MarkerSymType::DATA; continue; } if (IsData) { - SortedMarkerSymbols.push_back({SymInfo.Address, MarkerSymType::DATA}); + MarkerSymbols[SymInfo.Address] = MarkerSymType::DATA; LastAddr = SymInfo.Address; } } @@ -1283,27 +1308,24 @@ void RewriteInstance::discoverFileObjects() { BC->setHasSymbolsWithFileName(FileSymbols.size()); // Now that all the functions were created - adjust their boundaries. - adjustFunctionBoundaries(); + adjustFunctionBoundaries(MarkerSymbols); // Annotate functions with code/data markers in AArch64 - for (auto ISym = SortedMarkerSymbols.begin(); - ISym != SortedMarkerSymbols.end(); ++ISym) { - - auto *BF = - BC->getBinaryFunctionContainingAddress(ISym->Address, true, true); + for (auto &[Address, Type] : MarkerSymbols) { + auto *BF = BC->getBinaryFunctionContainingAddress(Address, true, true); if (!BF) { // Stray marker continue; } - const auto EntryOffset = ISym->Address - BF->getAddress(); - if (ISym->Type == MarkerSymType::CODE) { + const auto EntryOffset = Address - BF->getAddress(); + if (Type == MarkerSymType::CODE) { BF->markCodeAtOffset(EntryOffset); continue; } - if (ISym->Type == MarkerSymType::DATA) { + if (Type == MarkerSymType::DATA) { BF->markDataAtOffset(EntryOffset); - BC->AddressToConstantIslandMap[ISym->Address] = BF; + BC->AddressToConstantIslandMap[Address] = BF; continue; } llvm_unreachable("Unknown marker"); @@ -1832,7 +1854,8 @@ void RewriteInstance::disassemblePLT() { } } -void RewriteInstance::adjustFunctionBoundaries() { +void RewriteInstance::adjustFunctionBoundaries( + DenseMap &MarkerSyms) { for (auto BFI = BC->getBinaryFunctions().begin(), BFE = BC->getBinaryFunctions().end(); BFI != BFE; ++BFI) { @@ -1870,12 +1893,15 @@ void RewriteInstance::adjustFunctionBoundaries() { continue; } - // This is potentially another entry point into the function. - uint64_t EntryOffset = NextSymRefI->first - Function.getAddress(); - LLVM_DEBUG(dbgs() << "BOLT-DEBUG: adding entry point to function " - << Function << " at offset 0x" - << Twine::utohexstr(EntryOffset) << '\n'); - Function.addEntryPointAtOffset(EntryOffset); + auto It = MarkerSyms.find(NextSymRefI->first); + if (It == MarkerSyms.end() || It->second != MarkerSymType::DATA) { + // This is potentially another entry point into the function. + uint64_t EntryOffset = NextSymRefI->first - Function.getAddress(); + LLVM_DEBUG(dbgs() << "BOLT-DEBUG: adding entry point to function " + << Function << " at offset 0x" + << Twine::utohexstr(EntryOffset) << '\n'); + Function.addEntryPointAtOffset(EntryOffset); + } ++NextSymRefI; } @@ -2177,7 +2203,9 @@ void RewriteInstance::adjustCommandLineOptions() { if (!opts::KeepNops.getNumOccurrences()) opts::KeepNops = true; - // Linux kernel may resume execution after a trap instruction in some cases. + // Linux kernel may resume execution after a trap or x86 HLT instruction. + if (!opts::TerminalHLT.getNumOccurrences()) + opts::TerminalHLT = false; if (!opts::TerminalTrap.getNumOccurrences()) opts::TerminalTrap = false; } @@ -3570,7 +3598,7 @@ void RewriteInstance::postProcessFunctions() { if (opts::PrintAll || opts::PrintCFG) Function.print(BC->outs(), "after building cfg"); - if (opts::DumpDotAll) + if (opts::shouldDumpDot(Function)) Function.dumpGraphForPass("00_build-cfg"); if (opts::PrintLoopInfo) { diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index 973261765f951..72f95cea6fa1d 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -382,10 +382,9 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { // the list of successors of this basic block as appropriate. // Any of the above code sequences assume the fall-through basic block - // is a dead-end BRK instruction (any immediate operand is accepted). + // is a dead-end trap instruction. const BinaryBasicBlock *BreakBB = BB.getFallthrough(); - if (!BreakBB || BreakBB->empty() || - BreakBB->front().getOpcode() != AArch64::BRK) + if (!BreakBB || BreakBB->empty() || !isTrap(BreakBB->front())) return std::nullopt; // Iterate over the instructions of BB in reverse order, matching opcodes @@ -1744,6 +1743,34 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { Inst.addOperand(MCOperand::createImm(0)); } + bool isTrap(const MCInst &Inst) const override { + if (Inst.getOpcode() != AArch64::BRK) + return false; + // Only match the immediate values that are likely to indicate this BRK + // instruction is emitted to terminate the program immediately and not to + // be handled by a SIGTRAP handler, for example. + switch (Inst.getOperand(0).getImm()) { + case 0xc470: + case 0xc471: + case 0xc472: + case 0xc473: + // Explicit Pointer Authentication check failed, see + // AArch64AsmPrinter::emitPtrauthCheckAuthenticatedValue(). + return true; + case 0x1: + // __builtin_trap(), as emitted by Clang. + return true; + case 0x3e8: // decimal 1000 + // __builtin_trap(), as emitted by GCC. + return true; + default: + // Some constants may indicate intentionally recoverable break-points. + // This is the case at least for 0xf000, which is used by + // __builtin_debugtrap() supported by Clang. + return false; + } + } + bool isStorePair(const MCInst &Inst) const { const unsigned opcode = Inst.getOpcode(); diff --git a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp index a60c1a6bf156e..1842509dcc5e0 100644 --- a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp +++ b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp @@ -223,6 +223,10 @@ class X86MCPlusBuilder : public MCPlusBuilder { return Inst.getOpcode() == X86::ENDBR32 || Inst.getOpcode() == X86::ENDBR64; } + bool isX86HLT(const MCInst &Inst) const override { + return Inst.getOpcode() == X86::HLT; + } + int getPopSize(const MCInst &Inst) const override { switch (Inst.getOpcode()) { case X86::POP16r: diff --git a/bolt/test/AArch64/data-marker-invalidates-extra-entrypoint.s b/bolt/test/AArch64/data-marker-invalidates-extra-entrypoint.s new file mode 100644 index 0000000000000..3bcbcbba8a385 --- /dev/null +++ b/bolt/test/AArch64/data-marker-invalidates-extra-entrypoint.s @@ -0,0 +1,38 @@ +# This test is to ensure that we query data marker symbols to avoid +# misidentifying constant data island symbol as extra entry point. + +# RUN: %clang %cflags %s -o %t.so -Wl,-q -Wl,--init=_bar -Wl,--fini=_bar +# RUN: llvm-bolt %t.so -o %t.instr.so + + .text + .global _start + .type _start, %function +_start: + ret + + .text + .global _foo + .type _foo, %function +_foo: + cbz x1, _foo_2 +_foo_1: + add x1, x2, x0 + b _foo +_foo_2: + ret + +# None of these constant island symbols should be identified as extra entry +# point for function `_foo'. + .align 4 +_const1: .short 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 +_const2: .short 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0 +_const3: .short 0x04, 0x08, 0x0c, 0x20, 0x60, 0x80, 0xa0, 0xc0 + + .text + .global _bar + .type _bar, %function +_bar: + ret + + # Dummy relocation to force relocation mode + .reloc 0, R_AARCH64_NONE diff --git a/bolt/test/AArch64/unsupported-passes.test b/bolt/test/AArch64/unsupported-passes.test new file mode 100644 index 0000000000000..886fc1c574dcf --- /dev/null +++ b/bolt/test/AArch64/unsupported-passes.test @@ -0,0 +1,8 @@ +// Checks that non-fully supported passes on AArch64 are handled appropriately. + +// REQUIRES: system-linux,asserts,target=aarch64{{.*}} + +RUN: %clang %cflags %p/../Inputs/hello.c -o %t -Wl,-q +RUN: not llvm-bolt %t -o %t.bolt --frame-opt=all 2>&1 | FileCheck %s + +CHECK: BOLT-ERROR: frame-optimizer is supported only on X86 diff --git a/bolt/test/Inputs/multi-func.cpp b/bolt/test/Inputs/multi-func.cpp new file mode 100644 index 0000000000000..61c968fc27f98 --- /dev/null +++ b/bolt/test/Inputs/multi-func.cpp @@ -0,0 +1,24 @@ +#include + +// Multiple functions to test selective dumping +int add(int a, int b) { return a + b; } + +int multiply(int a, int b) { return a * b; } + +int main_helper() { + std::cout << "Helper function" << std::endl; + return 42; +} + +int main_secondary() { return add(5, 3); } + +void other_function() { std::cout << "Other function" << std::endl; } + +int main() { + int result = add(10, 20); + result = multiply(result, 2); + main_helper(); + main_secondary(); + other_function(); + return result; +} diff --git a/bolt/test/X86/hlt-terminator.s b/bolt/test/X86/hlt-terminator.s new file mode 100644 index 0000000000000..3f67182fdf432 --- /dev/null +++ b/bolt/test/X86/hlt-terminator.s @@ -0,0 +1,24 @@ +## Check that HLT instruction is handled differently depending on the flags. +## It's a terminator in the user-level code, but the execution can resume in +## ring 0. + +# RUN: %clang %cflags %s -static -o %t.exe -nostdlib +# RUN: llvm-bolt %t.exe --print-cfg --print-only=main --terminal-x86-hlt=0 \ +# RUN: -o %t.ring0 2>&1 | FileCheck %s --check-prefix=CHECK-RING0 +# RUN: llvm-bolt %t.exe --print-cfg --print-only=main \ +# RUN: -o %t.ring3 2>&1 | FileCheck %s --check-prefix=CHECK-RING3 +# RUN: llvm-objdump -d %t.ring0 --print-imm-hex | FileCheck %s --check-prefix=CHECK-BIN + +# CHECK-RING0: BB Count : 1 +# CHECK-RING3: BB Count : 2 + +# CHECK-BIN:
: +# CHECK-BIN-NEXT: f4 hlt +# CHECK-BIN-NEXT: c3 retq + +.global main + .type main, %function +main: + hlt + retq +.size main, .-main diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-address-checks.s b/bolt/test/binary-analysis/AArch64/gs-pauth-address-checks.s index 3f982ddaf6e38..74f276197923f 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-address-checks.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-address-checks.s @@ -31,7 +31,7 @@ resign_xpaci_good: xpaci x16 cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -46,7 +46,7 @@ resign_xpacd_good: xpacd x16 cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc473 1: pacda x0, x2 ret @@ -117,7 +117,7 @@ resign_xpaci_unrelated_auth_and_check: xpaci x16 cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x10, x2 ret @@ -139,7 +139,7 @@ resign_xpaci_wrong_pattern_1: xpaci x16 cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -157,7 +157,7 @@ resign_xpaci_wrong_pattern_2: xpaci x0 // x0 instead of x16 cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -174,7 +174,7 @@ resign_xpaci_wrong_pattern_3: xpaci x16 cmp x16, x16 // x16 instead of x0 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -191,7 +191,7 @@ resign_xpaci_wrong_pattern_4: xpaci x16 cmp x0, x0 // x0 instead of x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -208,7 +208,7 @@ resign_xpaci_wrong_pattern_5: mov x16, x16 // replace xpaci with a no-op instruction cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -228,7 +228,7 @@ resign_xpaclri_good: xpaclri cmp x30, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x30, x2 @@ -246,7 +246,7 @@ xpaclri_check_keeps_lr_safe: xpaclri // clobbers LR cmp x30, x16 b.eq 1f - brk 0x1234 // marks LR as trusted and safe-to-dereference + brk 0xc471 // marks LR as trusted and safe-to-dereference 1: ret // not reporting non-protected return .size xpaclri_check_keeps_lr_safe, .-xpaclri_check_keeps_lr_safe @@ -265,7 +265,7 @@ xpaclri_check_requires_safe_lr: xpaclri cmp x30, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: ret .size xpaclri_check_requires_safe_lr, .-xpaclri_check_requires_safe_lr @@ -283,7 +283,7 @@ resign_xpaclri_wrong_reg: xpaclri // ... but xpaclri still operates on x30 cmp x20, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x20, x2 @@ -303,7 +303,7 @@ resign_checked_not_authenticated: xpaci x16 cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -323,7 +323,7 @@ resign_checked_before_authenticated: xpaci x16 cmp x0, x16 b.eq 1f - brk 0x1234 + brk 0xc471 1: autib x0, x1 pacia x0, x2 @@ -339,7 +339,7 @@ resign_high_bits_tbz_good: autib x0, x1 eor x16, x0, x0, lsl #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -378,7 +378,7 @@ resign_high_bits_tbz_wrong_bit: autib x0, x1 eor x16, x0, x0, lsl #1 tbz x16, #63, 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -393,7 +393,7 @@ resign_high_bits_tbz_wrong_shift_amount: autib x0, x1 eor x16, x0, x0, lsl #2 tbz x16, #62, 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -408,7 +408,7 @@ resign_high_bits_tbz_wrong_shift_type: autib x0, x1 eor x16, x0, x0, lsr #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -423,7 +423,7 @@ resign_high_bits_tbz_wrong_pattern_1: autib x0, x1 eor x16, x0, x0, lsl #1 tbz x17, #62, 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -438,7 +438,7 @@ resign_high_bits_tbz_wrong_pattern_2: autib x0, x1 eor x16, x10, x0, lsl #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -453,7 +453,7 @@ resign_high_bits_tbz_wrong_pattern_3: autib x0, x1 eor x16, x0, x10, lsl #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc471 1: pacia x0, x2 ret @@ -648,7 +648,7 @@ many_checked_regs: xpacd x16 // ... cmp x2, x16 // ... b.eq 2f // end of basic block - brk 0x1234 + brk 0xc473 2: pacdza x0 pacdza x1 diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s index c314bc7cfe5a3..f44ba21b9d484 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s @@ -79,7 +79,7 @@ good_explicit_check: autia x0, x1 eor x16, x0, x0, lsl #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc470 1: ret .size good_explicit_check, .-good_explicit_check @@ -373,7 +373,7 @@ good_explicit_check_multi_bb: 1: eor x16, x0, x0, lsl #1 tbz x16, #62, 2f - brk 0x1234 + brk 0xc470 2: cbz x1, 3f nop @@ -685,8 +685,7 @@ good_address_arith_nocfg: .globl good_explicit_check_unrelated_reg .type good_explicit_check_unrelated_reg,@function good_explicit_check_unrelated_reg: -// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_explicit_check_unrelated_reg, basic block {{[^,]+}}, at address - // FIXME: The below instruction is not an authentication oracle +// CHECK-NOT: good_explicit_check_unrelated_reg autia x2, x3 // One of possible execution paths after this instruction // ends at BRK below, thus BRK used as a trap instruction // should formally "check everything" not to introduce @@ -694,7 +693,7 @@ good_explicit_check_unrelated_reg: autia x0, x1 eor x16, x0, x0, lsl #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc470 1: ldr x4, [x2] // Right before this instruction X2 is checked - this // should be propagated to the basic block ending with diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s b/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s index 3a4d383ec5bc6..4d4bb7b0fb251 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s @@ -57,7 +57,7 @@ good_sign_auted_checked_brk: autda x0, x2 eor x16, x0, x0, lsl #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc472 1: pacda x0, x1 ret @@ -351,7 +351,7 @@ good_sign_auted_checked_brk_multi_bb: 1: eor x16, x0, x0, lsl #1 tbz x16, #62, 2f - brk 0x1234 + brk 0xc472 2: cbz x4, 3f nop @@ -705,7 +705,7 @@ good_resign_with_increment_brk: add x0, x0, #8 eor x16, x0, x0, lsl #1 tbz x16, #62, 1f - brk 0x1234 + brk 0xc472 1: mov x2, x0 pacda x2, x1 diff --git a/bolt/test/binary-analysis/AArch64/trap-instructions.s b/bolt/test/binary-analysis/AArch64/trap-instructions.s new file mode 100644 index 0000000000000..7810b2d3c3626 --- /dev/null +++ b/bolt/test/binary-analysis/AArch64/trap-instructions.s @@ -0,0 +1,213 @@ +// RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe -Wl,--emit-relocs +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s + +// Test what instructions can be used to terminate the program abnormally +// on security violation. +// +// All test cases have the same structure: +// +// cbz x0, 1f // [a], ensures [c] is never reported as unreachable +// autia x2, x3 +// cbz x1, 2f // [b] +// [instruction under test] +// 1: +// ret // [c] +// 2: +// ldr x0, [x2] +// ret +// +// This is to handle three possible cases: the instruction under test may be +// considered by BOLT as +// * trapping (and thus no-return): after being authenticated, x2 is ether +// checked by LDR (if [b] is taken) or the program is terminated +// immediately without leaking x2 (if [b] falls through to the trapping +// instruction under test). Nothing is reported. +// * non-trapping, but no-return (such as calling abort()): x2 is leaked if [b] +// falls through. Authentication oracle is reported. +// * non-trapping and falling-through (i.e. a regular instruction): +// x2 is leaked by [c]. Authentication oracle is reported. + + .text + + .globl brk_key_ia + .type brk_key_ia,@function +brk_key_ia: +// CHECK-NOT: brk_key_ia + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0xc470 +1: + ret +2: + ldr x0, [x2] + ret + .size brk_key_ia, .-brk_key_ia + + .globl brk_key_ib + .type brk_key_ib,@function +brk_key_ib: +// CHECK-NOT: brk_key_ib + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0xc471 +1: + ret +2: + ldr x0, [x2] + ret + .size brk_key_ib, .-brk_key_ib + + .globl brk_key_da + .type brk_key_da,@function +brk_key_da: +// CHECK-NOT: brk_key_da + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0xc472 +1: + ret +2: + ldr x0, [x2] + ret + .size brk_key_da, .-brk_key_da + + .globl brk_key_db + .type brk_key_db,@function +brk_key_db: +// CHECK-NOT: brk_key_db + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0xc473 +1: + ret +2: + ldr x0, [x2] + ret + .size brk_key_db, .-brk_key_db + +// The immediate operand of BRK instruction may indicate whether the instruction +// is intended to be a non-recoverable trap: for example, for this code +// +// int test_trap(void) { +// __builtin_trap(); +// return 42; +// } +// int test_debugtrap(void) { +// __builtin_debugtrap(); +// return 42; +// } +// +// Clang produces the following assembly: +// +// test_trap: +// brk #0x1 +// test_debugtrap: +// brk #0xf000 +// mov w0, #42 +// ret +// +// In GCC, __builtin_trap() uses "brk 0x3e8" (i.e. decimal 1000) and +// __builtin_debugtrap() is not supported. +// +// At the time of writing these test cases, any BRK instruction is considered +// no-return by BOLT, thus it ends its basic block and prevents falling through +// to the next BB. +// FIXME: Make BOLT handle __builtin_debugtrap() properly from the CFG point +// of view. + + .globl brk_gcc_builtin_trap + .type brk_gcc_builtin_trap,@function +brk_gcc_builtin_trap: +// CHECK-NOT: brk_gcc_builtin_trap + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0x3e8 // __builtin_trap() +1: + ret +2: + ldr x0, [x2] + ret + .size brk_gcc_builtin_trap, .-brk_gcc_builtin_trap + + .globl brk_clang_builtin_trap + .type brk_clang_builtin_trap,@function +brk_clang_builtin_trap: +// CHECK-NOT: brk_clang_builtin_trap + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0x1 // __builtin_trap() +1: + ret +2: + ldr x0, [x2] + ret + .size brk_clang_builtin_trap, .-brk_clang_builtin_trap + + .globl brk_clang_builtin_debugtrap + .type brk_clang_builtin_debugtrap,@function +brk_clang_builtin_debugtrap: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function brk_clang_builtin_debugtrap, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x2, x3 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0xf000 // __builtin_debugtrap() +1: + ret +2: + ldr x0, [x2] + ret + .size brk_clang_builtin_debugtrap, .-brk_clang_builtin_debugtrap + +// Conservatively assume BRK with an unknown immediate operand as not suitable +// for terminating the program on security violation. + .globl brk_unknown_imm + .type brk_unknown_imm,@function +brk_unknown_imm: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function brk_unknown_imm, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x2, x3 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + brk 0x3572 +1: + ret +2: + ldr x0, [x2] + ret + .size brk_unknown_imm, .-brk_unknown_imm + +// Conservatively assume calling the abort() function may be an unsafe way to +// terminate the program, as there is some amount of instructions that would +// be executed when the program state is already tampered with. + .globl call_abort_fn + .type call_abort_fn,@function +call_abort_fn: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function call_abort_fn, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x2, x3 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + cbz x0, 1f + autia x2, x3 + cbz x1, 2f + b abort // a no-return tail call to abort() +1: + ret +2: + ldr x0, [x2] + ret + .size call_abort_fn, .-call_abort_fn + + .globl main + .type main,@function +main: + mov x0, 0 + ret + .size main, .-main diff --git a/bolt/test/dump-dot-func.test b/bolt/test/dump-dot-func.test new file mode 100644 index 0000000000000..510713dde6167 --- /dev/null +++ b/bolt/test/dump-dot-func.test @@ -0,0 +1,52 @@ +# Test the --dump-dot-func option with multiple functions +# (includes tests for both mangled/unmangled names) + +RUN: %clang++ %p/Inputs/multi-func.cpp -o %t.exe -Wl,-q + +# Test 1: --dump-dot-func with specific function name (mangled) +RUN: llvm-bolt %t.exe -o %t.bolt1 --dump-dot-func=_Z3addii -v=1 2>&1 | FileCheck %s --check-prefix=ADD + +# Test 2: --dump-dot-func with regex pattern (main.*) +RUN: llvm-bolt %t.exe -o %t.bolt2 --dump-dot-func="main.*" -v=1 2>&1 | FileCheck %s --check-prefix=MAIN-REGEX + +# Test 3: --dump-dot-func with multiple specific functions (mangled names) +RUN: llvm-bolt %t.exe -o %t.bolt3 --dump-dot-func=_Z3addii,_Z8multiplyii -v=1 2>&1 | FileCheck %s --check-prefix=MULTI + +# Test 4: No option specified should create no dot files +RUN: llvm-bolt %t.exe -o %t.bolt4 2>&1 | FileCheck %s --check-prefix=NONE + +# Test 5: --dump-dot-func with non-existent function +RUN: llvm-bolt %t.exe -o %t.bolt5 --dump-dot-func=nonexistent -v=1 2>&1 | FileCheck %s --check-prefix=NONEXISTENT + +# Test 6: Backward compatibility - --dump-dot-all should still work +RUN: llvm-bolt %t.exe -o %t.bolt6 --dump-dot-all -v=1 2>&1 | FileCheck %s --check-prefix=ALL + +# Test 7: Test with unmangled function name (main function) +RUN: llvm-bolt %t.exe -o %t.bolt7 --dump-dot-func=main -v=1 2>&1 | FileCheck %s --check-prefix=MAIN-UNMANGLED + +# Check that specific functions are dumped +ADD: BOLT-INFO: dumping CFG to _Z3addii-00_build-cfg.dot +ADD-NOT: BOLT-INFO: dumping CFG to main-00_build-cfg.dot +ADD-NOT: BOLT-INFO: dumping CFG to _Z8multiplyii-00_build-cfg.dot +ADD-NOT: BOLT-INFO: dumping CFG to _Z11main_helperv-00_build-cfg.dot + +MAIN-REGEX-DAG: BOLT-INFO: dumping CFG to main-00_build-cfg.dot +MAIN-REGEX-NOT: BOLT-INFO: dumping CFG to _Z3addii-00_build-cfg.dot +MAIN-REGEX-NOT: BOLT-INFO: dumping CFG to _Z8multiplyii-00_build-cfg.dot + +MULTI-DAG: BOLT-INFO: dumping CFG to _Z3addii-00_build-cfg.dot +MULTI-DAG: BOLT-INFO: dumping CFG to _Z8multiplyii-00_build-cfg.dot +MULTI-NOT: BOLT-INFO: dumping CFG to main-00_build-cfg.dot +MULTI-NOT: BOLT-INFO: dumping CFG to _Z11main_helperv-00_build-cfg.dot + +# Should be no dumping messages when no option is specified +NONE-NOT: BOLT-INFO: dumping CFG + +# Should be no dumping messages for non-existent function +NONEXISTENT-NOT: BOLT-INFO: dumping CFG + +ALL: BOLT-INFO: dumping CFG to main-00_build-cfg.dot + +MAIN-UNMANGLED: BOLT-INFO: dumping CFG to main-00_build-cfg.dot +MAIN-UNMANGLED-NOT: BOLT-INFO: dumping CFG to _Z3addii-00_build-cfg.dot +MAIN-UNMANGLED-NOT: BOLT-INFO: dumping CFG to _Z8multiplyii-00_build-cfg.dot \ No newline at end of file diff --git a/bolt/unittests/Core/CMakeLists.txt b/bolt/unittests/Core/CMakeLists.txt index 54e8ea10cda12..f10b0d9472067 100644 --- a/bolt/unittests/Core/CMakeLists.txt +++ b/bolt/unittests/Core/CMakeLists.txt @@ -11,6 +11,11 @@ add_bolt_unittest(CoreTests MemoryMaps.cpp DynoStats.cpp + # FIXME CoreTests uses `llvm::detail::TakeError(llvm::Error)`, but linking + # to LLVMTestingSupport introduces a transitive dependency on the + # dynamic LLVM library when LLVM_LINK_LLVM_DYLIB is ON. + ${LLVM_MAIN_SRC_DIR}/lib/Testing/Support/Error.cpp + DISABLE_LLVM_LINK_LLVM_DYLIB ) @@ -20,7 +25,6 @@ target_link_libraries(CoreTests LLVMBOLTRewrite LLVMBOLTProfile LLVMBOLTUtils - LLVMTestingSupport ) foreach (tgt ${BOLT_TARGETS_TO_BUILD}) diff --git a/bolt/unittests/Profile/CMakeLists.txt b/bolt/unittests/Profile/CMakeLists.txt index ce01c6c4b949e..7b3cbd2cad724 100644 --- a/bolt/unittests/Profile/CMakeLists.txt +++ b/bolt/unittests/Profile/CMakeLists.txt @@ -16,7 +16,6 @@ target_link_libraries(ProfileTests LLVMBOLTCore LLVMBOLTProfile LLVMTargetParser - LLVMTestingSupport ) foreach (tgt ${BOLT_TARGETS_TO_BUILD}) diff --git a/clang-tools-extra/clang-change-namespace/ChangeNamespace.cpp b/clang-tools-extra/clang-change-namespace/ChangeNamespace.cpp index 3e367ab1a5558..471ca45fb5a53 100644 --- a/clang-tools-extra/clang-change-namespace/ChangeNamespace.cpp +++ b/clang-tools-extra/clang-change-namespace/ChangeNamespace.cpp @@ -31,24 +31,9 @@ llvm::SmallVector splitSymbolName(llvm::StringRef Name) { return Splitted; } -SourceLocation startLocationForType(TypeLoc TLoc) { - // For elaborated types (e.g. `struct a::A`) we want the portion after the - // `struct` but including the namespace qualifier, `a::`. - if (TLoc.getTypeLocClass() == TypeLoc::Elaborated) { - NestedNameSpecifierLoc NestedNameSpecifier = - TLoc.castAs().getQualifierLoc(); - if (NestedNameSpecifier.getNestedNameSpecifier()) - return NestedNameSpecifier.getBeginLoc(); - TLoc = TLoc.getNextTypeLoc(); - } - return TLoc.getBeginLoc(); -} - SourceLocation endLocationForType(TypeLoc TLoc) { - // Dig past any namespace or keyword qualifications. - while (TLoc.getTypeLocClass() == TypeLoc::Elaborated || - TLoc.getTypeLocClass() == TypeLoc::Qualified) - TLoc = TLoc.getNextTypeLoc(); + if (auto QTL = TLoc.getAs()) + TLoc = QTL.getUnqualifiedLoc(); // The location for template specializations (e.g. Foo) includes the // templated types in its location range. We want to restrict this to just @@ -550,8 +535,8 @@ void ChangeNamespaceTool::run( Result.Nodes.getNodeAs( "nested_specifier_loc")) { SourceLocation Start = Specifier->getBeginLoc(); - SourceLocation End = endLocationForType(Specifier->getTypeLoc()); - fixTypeLoc(Result, Start, End, Specifier->getTypeLoc()); + SourceLocation End = endLocationForType(Specifier->castAsTypeLoc()); + fixTypeLoc(Result, Start, End, Specifier->castAsTypeLoc()); } else if (const auto *BaseInitializer = Result.Nodes.getNodeAs( "base_initializer")) { @@ -562,19 +547,16 @@ void ChangeNamespaceTool::run( // filtered by matchers in some cases, e.g. the type is templated. We should // handle the record type qualifier instead. TypeLoc Loc = *TLoc; - while (Loc.getTypeLocClass() == TypeLoc::Qualified) - Loc = Loc.getNextTypeLoc(); - if (Loc.getTypeLocClass() == TypeLoc::Elaborated) { - NestedNameSpecifierLoc NestedNameSpecifier = - Loc.castAs().getQualifierLoc(); - // FIXME: avoid changing injected class names. - if (auto *NNS = NestedNameSpecifier.getNestedNameSpecifier()) { - const Type *SpecifierType = NNS->getAsType(); - if (SpecifierType && SpecifierType->isRecordType()) - return; - } - } - fixTypeLoc(Result, startLocationForType(Loc), endLocationForType(Loc), Loc); + if (auto QTL = Loc.getAs()) + Loc = QTL.getUnqualifiedLoc(); + // FIXME: avoid changing injected class names. + if (NestedNameSpecifier NestedNameSpecifier = + Loc.getPrefix().getNestedNameSpecifier(); + NestedNameSpecifier.getKind() == NestedNameSpecifier::Kind::Type && + NestedNameSpecifier.getAsType()->isRecordType()) + return; + fixTypeLoc(Result, Loc.getNonElaboratedBeginLoc(), endLocationForType(Loc), + Loc); } else if (const auto *VarRef = Result.Nodes.getNodeAs("var_ref")) { const auto *Var = Result.Nodes.getNodeAs("var_decl"); @@ -588,10 +570,9 @@ void ChangeNamespaceTool::run( } else if (const auto *EnumConstRef = Result.Nodes.getNodeAs("enum_const_ref")) { // Do not rename the reference if it is already scoped by the EnumDecl name. - if (EnumConstRef->hasQualifier() && - EnumConstRef->getQualifier()->getKind() == - NestedNameSpecifier::SpecifierKind::TypeSpec && - EnumConstRef->getQualifier()->getAsType()->isEnumeralType()) + if (NestedNameSpecifier Qualifier = EnumConstRef->getQualifier(); + Qualifier.getKind() == NestedNameSpecifier::Kind::Type && + Qualifier.getAsType()->isEnumeralType()) return; const auto *EnumConstDecl = Result.Nodes.getNodeAs("enum_const_decl"); diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index a64cb5ea26a79..1ab40aacbfe09 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -144,17 +144,22 @@ Error MustacheHTMLGenerator::generateDocs( } else return JSONGenerator.takeError(); } + SmallString<128> JSONPath; + sys::path::native(RootDir.str() + "/json", JSONPath); StringMap JSONFileMap; { llvm::TimeTraceScope TS("Iterate JSON files"); std::error_code EC; - sys::fs::directory_iterator JSONIter(RootDir, EC); + sys::fs::directory_iterator JSONIter(JSONPath, EC); std::vector JSONFiles; JSONFiles.reserve(Infos.size()); if (EC) return createStringError("Failed to create directory iterator."); + SmallString<128> HTMLDirPath(RootDir.str() + "/html/"); + if (auto EC = sys::fs::create_directories(HTMLDirPath)) + return createFileError(HTMLDirPath, EC); while (JSONIter != sys::fs::directory_iterator()) { if (EC) return createFileError("Failed to iterate: " + JSONIter->path(), EC); @@ -177,14 +182,15 @@ Error MustacheHTMLGenerator::generateDocs( return Parsed.takeError(); std::error_code FileErr; - SmallString<16> HTMLPath(Path.begin(), Path.end()); - sys::path::replace_extension(HTMLPath, "html"); - raw_fd_ostream InfoOS(HTMLPath, FileErr, sys::fs::OF_None); + SmallString<128> HTMLFilePath(HTMLDirPath); + sys::path::append(HTMLFilePath, sys::path::filename(Path)); + sys::path::replace_extension(HTMLFilePath, "html"); + raw_fd_ostream InfoOS(HTMLFilePath, FileErr, sys::fs::OF_None); if (FileErr) return createFileOpenError(Path, FileErr); - if (Error Err = generateDocForJSON(*Parsed, sys::path::stem(HTMLPath), - HTMLPath, InfoOS, CDCtx)) + if (Error Err = generateDocForJSON(*Parsed, sys::path::stem(HTMLFilePath), + HTMLFilePath, InfoOS, CDCtx)) return Err; JSONIter.increment(EC); } diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp index 599b381cea60d..26794a5e34d02 100644 --- a/clang-tools-extra/clang-doc/JSONGenerator.cpp +++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp @@ -600,7 +600,9 @@ Error JSONGenerator::generateDocs( Info *Info = Group.getValue().get(); SmallString<128> Path; - sys::path::native(RootDir, Path); + auto RootDirStr = RootDir.str() + "/json"; + StringRef JSONDir = StringRef(RootDirStr); + sys::path::native(JSONDir, Path); if (!CreatedDirs.contains(Path)) { if (std::error_code Err = sys::fs::create_directories(Path); Err != std::error_code()) diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index de73f68b09386..bcab4f1b8a729 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -902,8 +902,8 @@ parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir, return; for (const CXXBaseSpecifier &B : D->bases()) { if (const RecordType *Ty = B.getType()->getAs()) { - if (const CXXRecordDecl *Base = - cast_or_null(Ty->getDecl()->getDefinition())) { + if (const CXXRecordDecl *Base = cast_or_null( + Ty->getOriginalDecl()->getDefinition())) { // Initialized without USR and name, this will be set in the following // if-else stmt. BaseRecordInfo BI( diff --git a/clang-tools-extra/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp b/clang-tools-extra/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp index bb48883f88815..1f30d27c0a54f 100644 --- a/clang-tools-extra/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp +++ b/clang-tools-extra/clang-include-fixer/find-all-symbols/FindAllSymbols.cpp @@ -216,8 +216,7 @@ void FindAllSymbols::registerMatchers(MatchFinder *MatchFinder) { // Uses of most types: just look at what the typeLoc refers to. MatchFinder->addMatcher( typeLoc(isExpansionInMainFile(), - loc(qualType(allOf(unless(elaboratedType()), - hasDeclaration(Types.bind("use")))))), + loc(qualType(hasDeclaration(Types.bind("use"))))), this); // Uses of typedefs: these are often transparent to hasDeclaration, so we need // to handle them explicitly. diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index 4ae2864d310d0..2064c7826da0c 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -424,6 +424,10 @@ ClangTidyASTConsumerFactory::createASTConsumer( FinderOptions.CheckProfiling.emplace(Profiling->Records); } + // Avoid processing system headers, unless the user explicitly requests it + if (!Context.getOptions().SystemHeaders.value_or(false)) + FinderOptions.IgnoreSystemHeaders = true; + std::unique_ptr Finder( new ast_matchers::MatchFinder(std::move(FinderOptions))); @@ -540,7 +544,7 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, ArrayRef InputFiles, llvm::IntrusiveRefCntPtr BaseFS, bool ApplyAnyFix, bool EnableCheckProfile, - llvm::StringRef StoreCheckProfile) { + llvm::StringRef StoreCheckProfile, bool Quiet) { ClangTool Tool(Compilations, InputFiles, std::make_shared(), BaseFS); @@ -577,8 +581,9 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, class ActionFactory : public FrontendActionFactory { public: ActionFactory(ClangTidyContext &Context, - IntrusiveRefCntPtr BaseFS) - : ConsumerFactory(Context, std::move(BaseFS)) {} + IntrusiveRefCntPtr BaseFS, + bool Quiet) + : ConsumerFactory(Context, std::move(BaseFS)), Quiet(Quiet) {} std::unique_ptr create() override { return std::make_unique(&ConsumerFactory); } @@ -589,6 +594,8 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, DiagnosticConsumer *DiagConsumer) override { // Explicitly ask to define __clang_analyzer__ macro. Invocation->getPreprocessorOpts().SetUpStaticAnalyzer = true; + if (Quiet) + Invocation->getDiagnosticOpts().ShowCarets = false; return FrontendActionFactory::runInvocation( Invocation, Files, PCHContainerOps, DiagConsumer); } @@ -607,9 +614,10 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, }; ClangTidyASTConsumerFactory ConsumerFactory; + bool Quiet; }; - ActionFactory Factory(Context, std::move(BaseFS)); + ActionFactory Factory(Context, std::move(BaseFS), Quiet); Tool.run(&Factory); return DiagConsumer.take(); } diff --git a/clang-tools-extra/clang-tidy/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h index 454261bbd6840..d37d68ec0a5b9 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.h +++ b/clang-tools-extra/clang-tidy/ClangTidy.h @@ -94,7 +94,8 @@ runClangTidy(clang::tidy::ClangTidyContext &Context, ArrayRef InputFiles, llvm::IntrusiveRefCntPtr BaseFS, bool ApplyAnyFix, bool EnableCheckProfile = false, - llvm::StringRef StoreCheckProfile = StringRef()); + llvm::StringRef StoreCheckProfile = StringRef(), + bool Quiet = false); /// Controls what kind of fixes clang-tidy is allowed to apply. enum FixBehaviour { diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp index f9d75978d0ea8..fac6e0418d163 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -533,7 +533,8 @@ void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) { Builder << reinterpret_cast(Info.getRawArg(Index)); break; case clang::DiagnosticsEngine::ak_nestednamespec: - Builder << reinterpret_cast(Info.getRawArg(Index)); + Builder << NestedNameSpecifier::getFromVoidPointer( + reinterpret_cast(Info.getRawArg(Index))); break; case clang::DiagnosticsEngine::ak_declcontext: Builder << reinterpret_cast(Info.getRawArg(Index)); diff --git a/clang-tools-extra/clang-tidy/abseil/DurationAdditionCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationAdditionCheck.h index 7f6b652058cfd..ac71f34fed180 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationAdditionCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationAdditionCheck.h @@ -22,6 +22,9 @@ class DurationAdditionCheck : public ClangTidyCheck { public: DurationAdditionCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.h index d759e1d8bb789..65ab7a38eb289 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationComparisonCheck.h @@ -22,6 +22,9 @@ class DurationComparisonCheck : public ClangTidyCheck { public: DurationComparisonCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/DurationConversionCastCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationConversionCastCheck.h index fea9f703bae65..a898ba0483966 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationConversionCastCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationConversionCastCheck.h @@ -22,6 +22,9 @@ class DurationConversionCastCheck : public ClangTidyCheck { public: DurationConversionCastCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.h index 6394b3f2b4675..e7c3985a7fd92 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationFactoryFloatCheck.h @@ -24,6 +24,9 @@ class DurationFactoryFloatCheck : public ClangTidyCheck { public: DurationFactoryFloatCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.h index 40ffb30b4769c..f5f088c49897d 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationFactoryScaleCheck.h @@ -24,6 +24,9 @@ class DurationFactoryScaleCheck : public ClangTidyCheck { public: DurationFactoryScaleCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/DurationSubtractionCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationSubtractionCheck.h index 17f7853adabd6..c865f2f842a0d 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationSubtractionCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationSubtractionCheck.h @@ -22,6 +22,9 @@ class DurationSubtractionCheck : public ClangTidyCheck { public: DurationSubtractionCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/DurationUnnecessaryConversionCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationUnnecessaryConversionCheck.h index aa25c5f43e0f0..fc9cf23459425 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationUnnecessaryConversionCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationUnnecessaryConversionCheck.h @@ -22,6 +22,9 @@ class DurationUnnecessaryConversionCheck : public ClangTidyCheck { public: DurationUnnecessaryConversionCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/TimeComparisonCheck.h b/clang-tools-extra/clang-tidy/abseil/TimeComparisonCheck.h index 0c64708758b46..bf22977e9d0df 100644 --- a/clang-tools-extra/clang-tidy/abseil/TimeComparisonCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/TimeComparisonCheck.h @@ -22,6 +22,9 @@ class TimeComparisonCheck : public ClangTidyCheck { public: TimeComparisonCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h b/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h index c947f6bca7f31..9e2ec1c8def20 100644 --- a/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h @@ -22,6 +22,9 @@ class TimeSubtractionCheck : public ClangTidyCheck { public: TimeSubtractionCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; diff --git a/clang-tools-extra/clang-tidy/add_new_check.py b/clang-tools-extra/clang-tidy/add_new_check.py index e366f10053535..2b51a1dc40ebc 100755 --- a/clang-tools-extra/clang-tidy/add_new_check.py +++ b/clang-tools-extra/clang-tidy/add_new_check.py @@ -89,13 +89,9 @@ def write_header( + check_name_camel.upper() + "_H" ) - f.write("//===--- ") - f.write(os.path.basename(filename)) - f.write(" - clang-tidy ") - f.write("-" * max(0, 42 - len(os.path.basename(filename)))) - f.write("*- C++ -*-===//") f.write( """ +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -145,13 +141,9 @@ def write_implementation( filename = os.path.join(module_path, check_name_camel) + ".cpp" print("Creating %s..." % filename) with io.open(filename, "w", encoding="utf8", newline="\n") as f: - f.write("//===--- ") - f.write(os.path.basename(filename)) - f.write(" - clang-tidy ") - f.write("-" * max(0, 51 - len(os.path.basename(filename)))) - f.write("-===//") f.write( """ +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/clang-tools-extra/clang-tidy/bugprone/CrtpConstructorAccessibilityCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/CrtpConstructorAccessibilityCheck.cpp index 6565fa3f7c85b..0625468d9da88 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CrtpConstructorAccessibilityCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/CrtpConstructorAccessibilityCheck.cpp @@ -43,7 +43,8 @@ static bool isDerivedClassBefriended(const CXXRecordDecl *CRTP, return false; } - return FriendType->getType()->getAsCXXRecordDecl() == Derived; + return declaresSameEntity(FriendType->getType()->getAsCXXRecordDecl(), + Derived); }); } @@ -55,7 +56,8 @@ getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP, CRTP->getTemplateArgs().asArray(), [&](const TemplateArgument &Arg) { ++Idx; return Arg.getKind() == TemplateArgument::Type && - Arg.getAsType()->getAsCXXRecordDecl() == Derived; + declaresSameEntity(Arg.getAsType()->getAsCXXRecordDecl(), + Derived); }); return AnyOf ? CRTP->getSpecializedTemplate() diff --git a/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.h b/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.h index 3044304892029..981e9b571a618 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.h @@ -13,14 +13,16 @@ namespace clang::tidy::bugprone { -/// Detect dangling references in value handlers like -/// std::experimental::string_view. +/// Detect dangling references in value handlers like std::string_view. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/dangling-handle.html class DanglingHandleCheck : public ClangTidyCheck { public: DanglingHandleCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; void storeOptions(ClangTidyOptions::OptionMap &Opts) override; diff --git a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp index a179d4bf66b4d..7f1eeef8ea0fd 100644 --- a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp @@ -577,7 +577,7 @@ approximateImplicitConversion(const TheCheck &Check, QualType LType, ImplicitConversionModellingMode ImplicitMode); static inline bool isUselessSugar(const Type *T) { - return isa(T); + return isa(T); } namespace { @@ -1040,7 +1040,9 @@ approximateStandardConversionSequence(const TheCheck &Check, QualType From, const auto *ToRecord = To->getAsCXXRecordDecl(); if (isDerivedToBase(FromRecord, ToRecord)) { LLVM_DEBUG(llvm::dbgs() << "--- approximateStdConv. Derived To Base.\n"); - WorkType = QualType{ToRecord->getTypeForDecl(), FastQualifiersToApply}; + WorkType = QualType{ + ToRecord->getASTContext().getCanonicalTagType(ToRecord)->getTypePtr(), + FastQualifiersToApply}; } if (Ctx.getLangOpts().CPlusPlus17 && FromPtr && ToPtr) { @@ -1072,9 +1074,9 @@ approximateStandardConversionSequence(const TheCheck &Check, QualType From, WorkType = To; } - if (WorkType == To) { + if (Ctx.hasSameType(WorkType, To)) { LLVM_DEBUG(llvm::dbgs() << "<<< approximateStdConv. Reached 'To' type.\n"); - return {WorkType}; + return {Ctx.getCommonSugaredType(WorkType, To)}; } LLVM_DEBUG(llvm::dbgs() << "<<< approximateStdConv. Did not reach 'To'.\n"); @@ -1219,7 +1221,7 @@ tryConversionOperators(const TheCheck &Check, const CXXRecordDecl *RD, if (std::optional SelectedConversion = ConversionSet()) { - QualType RecordType{RD->getTypeForDecl(), 0}; + CanQualType RecordType = RD->getASTContext().getCanonicalTagType(RD); ConversionSequence Result{RecordType, ToType}; // The conversion from the operator call's return type to ToType was @@ -1270,7 +1272,7 @@ tryConvertingConstructors(const TheCheck &Check, QualType FromType, if (std::optional SelectedConversion = ConversionSet()) { - QualType RecordType{RD->getTypeForDecl(), 0}; + CanQualType RecordType = RD->getASTContext().getCanonicalTagType(RD); ConversionSequence Result{FromType, RecordType}; Result.AfterFirstStandard = SelectedConversion->Seq.AfterFirstStandard; @@ -1573,6 +1575,10 @@ template using ParamToSmallSetMap = llvm::DenseMap>; +template +using ParamToSmallPtrSetMap = + llvm::DenseMap>; + /// Returns whether the sets mapped to the two elements in the map have at /// least one element in common. template @@ -1697,7 +1703,7 @@ class PassedToSameFunction { /// Implements the heuristic that marks two parameters related if the same /// member is accessed (referred to) inside the current function's body. class AccessedSameMemberOf { - ParamToSmallSetMap AccessedMembers; + ParamToSmallPtrSetMap AccessedMembers; public: void setup(const FunctionDecl *FD) { diff --git a/clang-tools-extra/clang-tidy/bugprone/FoldInitTypeCheck.h b/clang-tools-extra/clang-tidy/bugprone/FoldInitTypeCheck.h index af8c1e50ef86c..435c440ddd29f 100644 --- a/clang-tools-extra/clang-tidy/bugprone/FoldInitTypeCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/FoldInitTypeCheck.h @@ -26,6 +26,9 @@ class FoldInitTypeCheck : public ClangTidyCheck { public: FoldInitTypeCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; diff --git a/clang-tools-extra/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.cpp index 75ef628436738..070ed04efffc4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.cpp @@ -69,10 +69,9 @@ void ForwardDeclarationNamespaceCheck::check( // struct B { friend A; }; // \endcode // `A` will not be marked as "referenced" in the AST. - if (const TypeSourceInfo *Tsi = Decl->getFriendType()) { - QualType Desugared = Tsi->getType().getDesugaredType(*Result.Context); - FriendTypes.insert(Desugared.getTypePtr()); - } + if (const TypeSourceInfo *Tsi = Decl->getFriendType()) + FriendTypes.insert( + Tsi->getType()->getCanonicalTypeUnqualified().getTypePtr()); } } @@ -119,7 +118,9 @@ void ForwardDeclarationNamespaceCheck::onEndOfTranslationUnit() { if (CurDecl->hasDefinition() || CurDecl->isReferenced()) { continue; // Skip forward declarations that are used/referenced. } - if (FriendTypes.contains(CurDecl->getTypeForDecl())) { + if (FriendTypes.contains(CurDecl->getASTContext() + .getCanonicalTagType(CurDecl) + ->getTypePtr())) { continue; // Skip forward declarations referenced as friend. } if (CurDecl->getLocation().isMacroID() || diff --git a/clang-tools-extra/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.cpp index 00e8f7e514368..10b747e17e2ad 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.cpp @@ -33,21 +33,17 @@ AST_MATCHER(QualType, isEnableIf) { BaseType = BaseType->getPointeeType().getTypePtr(); } // Case: type parameter dependent (enable_if>). - if (const auto *Dependent = BaseType->getAs()) { - BaseType = Dependent->getQualifier()->getAsType(); - } + if (const auto *Dependent = BaseType->getAs()) + BaseType = Dependent->getQualifier().getAsType(); if (!BaseType) return false; if (CheckTemplate(BaseType->getAs())) return true; // Case: enable_if_t< >. - if (const auto *Elaborated = BaseType->getAs()) { - if (const auto *Q = Elaborated->getQualifier()) - if (const auto *Qualifier = Q->getAsType()) { - if (CheckTemplate(Qualifier->getAs())) { - return true; // Case: enable_if< >::type. - } - } - } + if (const auto *TT = BaseType->getAs()) + if (NestedNameSpecifier Q = TT->getQualifier(); + Q.getKind() == NestedNameSpecifier::Kind::Type) + if (CheckTemplate(Q.getAsType()->getAs())) + return true; // Case: enable_if< >::type. return false; } AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument, diff --git a/clang-tools-extra/clang-tidy/bugprone/IncorrectEnableIfCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/IncorrectEnableIfCheck.cpp index 75f1107904fce..07cd90d64c2a4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/IncorrectEnableIfCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/IncorrectEnableIfCheck.cpp @@ -32,13 +32,10 @@ AST_MATCHER_P(TemplateTypeParmDecl, hasUnnamedDefaultArgument, void IncorrectEnableIfCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( templateTypeParmDecl( - hasUnnamedDefaultArgument( - elaboratedTypeLoc( - hasNamedTypeLoc(templateSpecializationTypeLoc( - loc(qualType(hasDeclaration(namedDecl( - hasName("::std::enable_if")))))) - .bind("enable_if_specialization"))) - .bind("elaborated"))) + hasUnnamedDefaultArgument(templateSpecializationTypeLoc( + loc(qualType(hasDeclaration(namedDecl( + hasName("::std::enable_if")))))) + .bind("enable_if_specialization"))) .bind("enable_if"), this); } @@ -46,13 +43,11 @@ void IncorrectEnableIfCheck::registerMatchers(MatchFinder *Finder) { void IncorrectEnableIfCheck::check(const MatchFinder::MatchResult &Result) { const auto *EnableIf = Result.Nodes.getNodeAs("enable_if"); - const auto *ElaboratedLoc = - Result.Nodes.getNodeAs("elaborated"); const auto *EnableIfSpecializationLoc = Result.Nodes.getNodeAs( "enable_if_specialization"); - if (!EnableIf || !ElaboratedLoc || !EnableIfSpecializationLoc) + if (!EnableIf || !EnableIfSpecializationLoc) return; const SourceManager &SM = *Result.SourceManager; @@ -62,8 +57,10 @@ void IncorrectEnableIfCheck::check(const MatchFinder::MatchResult &Result) { auto Diag = diag(EnableIf->getBeginLoc(), "incorrect std::enable_if usage detected; use " "'typename std::enable_if<...>::type'"); + // FIXME: This should handle the enable_if specialization already having an + // elaborated keyword. if (!getLangOpts().CPlusPlus20) { - Diag << FixItHint::CreateInsertion(ElaboratedLoc->getBeginLoc(), + Diag << FixItHint::CreateInsertion(EnableIfSpecializationLoc->getBeginLoc(), "typename "); } Diag << FixItHint::CreateInsertion(RAngleLoc.getLocWithOffset(1), "::type"); diff --git a/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp index 4b495e3877000..cda9c4e7a6e58 100644 --- a/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp @@ -188,7 +188,7 @@ static bool isKnownToHaveValue(const Expr &Cond, const ASTContext &Ctx, /// \return true iff all `CallExprs` visited have callees; false otherwise /// indicating there is an unresolved indirect call. static bool populateCallees(const Stmt *StmtNode, - llvm::SmallSet &Callees) { + llvm::SmallPtrSet &Callees) { if (const auto *Call = dyn_cast(StmtNode)) { const Decl *Callee = Call->getDirectCallee(); @@ -212,7 +212,7 @@ static bool populateCallees(const Stmt *StmtNode, /// returns true iff `SCC` contains `Func` and its' function set overlaps with /// `Callees` static bool overlap(ArrayRef SCC, - const llvm::SmallSet &Callees, + const llvm::SmallPtrSet &Callees, const Decl *Func) { bool ContainsFunc = false, Overlap = false; @@ -264,7 +264,7 @@ static bool hasRecursionOverStaticLoopCondVariables(const Expr *Cond, if (!hasStaticLocalVariable(Cond)) return false; - llvm::SmallSet CalleesInLoop; + llvm::SmallPtrSet CalleesInLoop; if (!populateCallees(LoopStmt, CalleesInLoop)) { // If there are unresolved indirect calls, we assume there could diff --git a/clang-tools-extra/clang-tidy/bugprone/InvalidEnumDefaultInitializationCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/InvalidEnumDefaultInitializationCheck.cpp index f903e631e0be0..7d92ef301aec3 100644 --- a/clang-tools-extra/clang-tidy/bugprone/InvalidEnumDefaultInitializationCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/InvalidEnumDefaultInitializationCheck.cpp @@ -67,15 +67,15 @@ class FindEnumMember : public TypeVisitor { return Visit(T->getElementType().getTypePtr()); } bool VisitEnumType(const EnumType *T) { - if (isCompleteAndHasNoZeroValue(T->getDecl())) { + if (isCompleteAndHasNoZeroValue(T->getOriginalDecl())) { FoundEnum = T; return true; } return false; } bool VisitRecordType(const RecordType *T) { - const RecordDecl *RD = T->getDecl(); - if (RD->isUnion()) + const RecordDecl *RD = T->getOriginalDecl()->getDefinition(); + if (!RD || RD->isUnion()) return false; auto VisitField = [this](const FieldDecl *F) { return Visit(F->getType().getTypePtr()); @@ -125,7 +125,7 @@ void InvalidEnumDefaultInitializationCheck::check( if (!Finder.Visit(InitList->getArrayFiller()->getType().getTypePtr())) return; InitExpr = InitList; - Enum = Finder.FoundEnum->getDecl(); + Enum = Finder.FoundEnum->getOriginalDecl(); } if (!InitExpr || !Enum) diff --git a/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.h b/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.h index dab64f74aa6ca..04ba3596167e3 100644 --- a/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.h @@ -32,6 +32,9 @@ class LambdaFunctionNameCheck : public ClangTidyCheck { using SourceRangeSet = std::set; LambdaFunctionNameCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; diff --git a/clang-tools-extra/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp index bfa2ab51a6d03..5dc988d6662df 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp @@ -39,24 +39,31 @@ static void replaceMoveWithForward(const UnresolvedLookupExpr *Callee, // std::move(). This will hopefully prevent erroneous replacements if the // code does unusual things (e.g. create an alias for std::move() in // another namespace). - NestedNameSpecifier *NNS = Callee->getQualifier(); - if (!NNS) { + NestedNameSpecifier NNS = Callee->getQualifier(); + switch (NNS.getKind()) { + case NestedNameSpecifier::Kind::Null: // Called as "move" (i.e. presumably the code had a "using std::move;"). // We still conservatively put a "std::" in front of the forward because // we don't know whether the code also had a "using std::forward;". Diag << FixItHint::CreateReplacement(CallRange, "std::" + ForwardName); - } else if (const NamespaceBaseDecl *Namespace = NNS->getAsNamespace()) { + break; + case NestedNameSpecifier::Kind::Namespace: { + auto [Namespace, Prefix] = NNS.getAsNamespaceAndPrefix(); if (Namespace->getName() == "std") { - if (!NNS->getPrefix()) { + if (!Prefix) { // Called as "std::move". Diag << FixItHint::CreateReplacement(CallRange, "std::" + ForwardName); - } else if (NNS->getPrefix()->getKind() == NestedNameSpecifier::Global) { + } else if (Prefix.getKind() == NestedNameSpecifier::Kind::Global) { // Called as "::std::move". Diag << FixItHint::CreateReplacement(CallRange, "::std::" + ForwardName); } } + break; + } + default: + return; } } } diff --git a/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp index b68888cb5b928..6344b4bb6271e 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp @@ -15,14 +15,12 @@ using namespace clang::ast_matchers; namespace clang::tidy::bugprone { -namespace { - // Determine if the result of an expression is "stored" in some way. // It is true if the value is stored into a variable or used as initialization // or passed to a function or constructor. // For this use case compound assignments are not counted as a "store" (the 'E' // expression should have pointer type). -bool isExprValueStored(const Expr *E, ASTContext &C) { +static bool isExprValueStored(const Expr *E, ASTContext &C) { E = E->IgnoreParenCasts(); // Get first non-paren, non-cast parent. ParentMapContext &PMap = C.getParentMapContext(); @@ -49,6 +47,8 @@ bool isExprValueStored(const Expr *E, ASTContext &C) { return isa(ParentE); } +namespace { + AST_MATCHER_P(CXXTryStmt, hasHandlerFor, ast_matchers::internal::Matcher, InnerMatcher) { for (unsigned NH = Node.getNumHandlers(), I = 0; I < NH; ++I) { diff --git a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp index 53782231b6421..249c77ca0c432 100644 --- a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp @@ -555,15 +555,22 @@ bool NarrowingConversionsCheck::handleConditionalOperator( // We have an expression like so: `output = cond ? lhs : rhs` // From the point of view of narrowing conversion we treat it as two // expressions `output = lhs` and `output = rhs`. - handleBinaryOperator(Context, CO->getLHS()->getExprLoc(), Lhs, - *CO->getLHS()); - handleBinaryOperator(Context, CO->getRHS()->getExprLoc(), Lhs, - *CO->getRHS()); + handleConditionalOperatorArgument(Context, Lhs, CO->getLHS()); + handleConditionalOperatorArgument(Context, Lhs, CO->getRHS()); return true; } return false; } +void NarrowingConversionsCheck::handleConditionalOperatorArgument( + const ASTContext &Context, const Expr &Lhs, const Expr *Arg) { + if (const auto *ICE = llvm::dyn_cast(Arg)) + if (!Arg->getIntegerConstantExpr(Context)) + Arg = ICE->getSubExpr(); + + handleBinaryOperator(Context, Arg->getExprLoc(), Lhs, *Arg); +} + void NarrowingConversionsCheck::handleImplicitCast( const ASTContext &Context, const ImplicitCastExpr &Cast) { if (Cast.getExprLoc().isMacroID()) diff --git a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h index 20403f920b925..116a8cba8d321 100644 --- a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.h @@ -85,6 +85,8 @@ class NarrowingConversionsCheck : public ClangTidyCheck { bool handleConditionalOperator(const ASTContext &Context, const Expr &Lhs, const Expr &Rhs); + void handleConditionalOperatorArgument(const ASTContext &Context, + const Expr &Lhs, const Expr *Arg); void handleImplicitCast(const ASTContext &Context, const ImplicitCastExpr &Cast); diff --git a/clang-tools-extra/clang-tidy/bugprone/OptionalValueConversionCheck.h b/clang-tools-extra/clang-tidy/bugprone/OptionalValueConversionCheck.h index b044219948988..888d29fc937bd 100644 --- a/clang-tools-extra/clang-tidy/bugprone/OptionalValueConversionCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/OptionalValueConversionCheck.h @@ -23,6 +23,9 @@ namespace clang::tidy::bugprone { class OptionalValueConversionCheck : public ClangTidyCheck { public: OptionalValueConversionCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; void storeOptions(ClangTidyOptions::OptionMap &Opts) override; diff --git a/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.h b/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.h index f552965e1876b..293069fd24665 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.h @@ -21,6 +21,9 @@ class ParentVirtualCallCheck : public ClangTidyCheck { public: ParentVirtualCallCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/bugprone/SizeofContainerCheck.h b/clang-tools-extra/clang-tidy/bugprone/SizeofContainerCheck.h index 7227c3d106f43..f50ce99c6d4c0 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SizeofContainerCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/SizeofContainerCheck.h @@ -22,6 +22,9 @@ class SizeofContainerCheck : public ClangTidyCheck { public: SizeofContainerCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp index 88e048e65d4e8..8da6227e172cd 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp @@ -425,7 +425,7 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { "suspicious usage of 'sizeof(array)/sizeof(...)';" " denominator differs from the size of array elements") << E->getLHS()->getSourceRange() << E->getRHS()->getSourceRange(); - } else if (NumTy && DenomTy && NumTy == DenomTy && + } else if (NumTy && DenomTy && Ctx.hasSameType(NumTy, DenomTy) && !NumTy->isDependentType()) { // Dependent type should not be compared. diag(E->getOperatorLoc(), @@ -434,7 +434,7 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { << E->getLHS()->getSourceRange() << E->getRHS()->getSourceRange(); } else if (!WarnOnSizeOfPointer) { // When 'WarnOnSizeOfPointer' is enabled, these messages become redundant: - if (PointedTy && DenomTy && PointedTy == DenomTy) { + if (PointedTy && DenomTy && Ctx.hasSameType(PointedTy, DenomTy)) { diag(E->getOperatorLoc(), "suspicious usage of 'sizeof(...)/sizeof(...)'; size of pointer " "is divided by size of pointed type") @@ -463,7 +463,8 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { const auto *SizeOfExpr = Result.Nodes.getNodeAs("sizeof-ptr-mul-expr"); - if ((LPtrTy == RPtrTy) && (LPtrTy == SizeofArgTy)) { + if (Ctx.hasSameType(LPtrTy, RPtrTy) && + Ctx.hasSameType(LPtrTy, SizeofArgTy)) { diag(SizeOfExpr->getBeginLoc(), "suspicious usage of 'sizeof(...)' in " "pointer arithmetic") << SizeOfExpr->getSourceRange() << E->getOperatorLoc() @@ -477,7 +478,8 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { const auto *SizeOfExpr = Result.Nodes.getNodeAs("sizeof-ptr-div-expr"); - if ((LPtrTy == RPtrTy) && (LPtrTy == SizeofArgTy)) { + if (Ctx.hasSameType(LPtrTy, RPtrTy) && + Ctx.hasSameType(LPtrTy, SizeofArgTy)) { diag(SizeOfExpr->getBeginLoc(), "suspicious usage of 'sizeof(...)' in " "pointer arithmetic") << SizeOfExpr->getSourceRange() << E->getOperatorLoc() diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp index 81c38d07a0c7e..5b1b28dbfbadd 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp @@ -14,10 +14,8 @@ using namespace clang::ast_matchers; namespace clang::tidy::bugprone { -namespace { - -bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx, - const StringLiteral *Lit) { +static bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx, + const StringLiteral *Lit) { // String literals surrounded by parentheses are assumed to be on purpose. // i.e.: const char* Array[] = { ("a" "b" "c"), "d", [...] }; @@ -58,6 +56,8 @@ bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx, return false; } +namespace { + AST_MATCHER_P(StringLiteral, isConcatenatedLiteral, unsigned, MaxConcatenatedTokens) { return Node.getNumConcatenated() > 1 && diff --git a/clang-tools-extra/clang-tidy/cert/StrToNumCheck.cpp b/clang-tools-extra/clang-tidy/cert/StrToNumCheck.cpp index 3b59d2357fe29..95536bb1cfdb2 100644 --- a/clang-tools-extra/clang-tidy/cert/StrToNumCheck.cpp +++ b/clang-tools-extra/clang-tidy/cert/StrToNumCheck.cpp @@ -46,7 +46,9 @@ enum class ConversionKind { ToLongDouble }; -ConversionKind classifyConversionFunc(const FunctionDecl *FD) { +} // namespace + +static ConversionKind classifyConversionFunc(const FunctionDecl *FD) { return llvm::StringSwitch(FD->getName()) .Cases("atoi", "atol", ConversionKind::ToInt) .Case("atoll", ConversionKind::ToLongInt) @@ -54,8 +56,8 @@ ConversionKind classifyConversionFunc(const FunctionDecl *FD) { .Default(ConversionKind::None); } -ConversionKind classifyFormatString(StringRef Fmt, const LangOptions &LO, - const TargetInfo &TI) { +static ConversionKind classifyFormatString(StringRef Fmt, const LangOptions &LO, + const TargetInfo &TI) { // Scan the format string for the first problematic format specifier, then // report that as the conversion type. This will miss additional conversion // specifiers, but that is acceptable behavior. @@ -128,7 +130,7 @@ ConversionKind classifyFormatString(StringRef Fmt, const LangOptions &LO, return H.get(); } -StringRef classifyConversionType(ConversionKind K) { +static StringRef classifyConversionType(ConversionKind K) { switch (K) { case ConversionKind::None: llvm_unreachable("Unexpected conversion kind"); @@ -148,7 +150,7 @@ StringRef classifyConversionType(ConversionKind K) { llvm_unreachable("Unknown conversion kind"); } -StringRef classifyReplacement(ConversionKind K) { +static StringRef classifyReplacement(ConversionKind K) { switch (K) { case ConversionKind::None: llvm_unreachable("Unexpected conversion kind"); @@ -173,7 +175,6 @@ StringRef classifyReplacement(ConversionKind K) { } llvm_unreachable("Unknown conversion kind"); } -} // unnamed namespace void StrToNumCheck::check(const MatchFinder::MatchResult &Result) { const auto *Call = Result.Nodes.getNodeAs("expr"); diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt index 2fb4d7f1d7349..0abb000991859 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -21,6 +21,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule STATIC OwningMemoryCheck.cpp PreferMemberInitializerCheck.cpp ProBoundsArrayToPointerDecayCheck.cpp + ProBoundsAvoidUncheckedContainerAccess.cpp ProBoundsConstantArrayIndexCheck.cpp ProBoundsPointerArithmeticCheck.cpp ProTypeConstCastCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp index 4b3b7bf963fdc..cc1ae156eef3e 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -36,6 +36,7 @@ #include "OwningMemoryCheck.h" #include "PreferMemberInitializerCheck.h" #include "ProBoundsArrayToPointerDecayCheck.h" +#include "ProBoundsAvoidUncheckedContainerAccess.h" #include "ProBoundsConstantArrayIndexCheck.h" #include "ProBoundsPointerArithmeticCheck.h" #include "ProTypeConstCastCheck.h" @@ -107,6 +108,8 @@ class CppCoreGuidelinesModule : public ClangTidyModule { "cppcoreguidelines-prefer-member-initializer"); CheckFactories.registerCheck( "cppcoreguidelines-pro-bounds-array-to-pointer-decay"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-avoid-unchecked-container-access"); CheckFactories.registerCheck( "cppcoreguidelines-pro-bounds-constant-array-index"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/NoSuspendWithLockCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/NoSuspendWithLockCheck.cpp index ca293178c78b4..29470b1f725fb 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/NoSuspendWithLockCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/NoSuspendWithLockCheck.cpp @@ -23,9 +23,9 @@ void NoSuspendWithLockCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { } void NoSuspendWithLockCheck::registerMatchers(MatchFinder *Finder) { - auto LockType = elaboratedType(namesType(templateSpecializationType( + auto LockType = templateSpecializationType( hasDeclaration(namedDecl(matchers::matchesAnyListedName( - utils::options::parseStringList(LockGuards))))))); + utils::options::parseStringList(LockGuards))))); StatementMatcher Lock = declStmt(has(varDecl(hasType(LockType)).bind("lock-decl"))) diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp index 593a4f85d1309..79cd4bbcc9a60 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp @@ -191,6 +191,9 @@ void PreferMemberInitializerCheck::check( if (!AssignmentToMember) continue; const FieldDecl *Field = AssignmentToMember->Field; + // Skip if the field is inherited from a base class. + if (Field->getParent() != Class) + continue; const Expr *InitValue = AssignmentToMember->Init; updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields); if (!canAdvanceAssignment(AssignedFields[Field])) diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.cpp new file mode 100644 index 0000000000000..35f432efa88ca --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.cpp @@ -0,0 +1,262 @@ +//===--- ProBoundsAvoidUncheckedContainerAccess.cpp - clang-tidy ----------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsAvoidUncheckedContainerAccess.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::cppcoreguidelines { + +static constexpr llvm::StringRef DefaultExclusionStr = + "::std::map;::std::unordered_map;::std::flat_map"; + +ProBoundsAvoidUncheckedContainerAccess::ProBoundsAvoidUncheckedContainerAccess( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ExcludedClasses(utils::options::parseStringList( + Options.get("ExcludeClasses", DefaultExclusionStr))), + FixMode(Options.get("FixMode", None)), + FixFunction(Options.get("FixFunction", "gsl::at")), + FixFunctionEmptyArgs(Options.get("FixFunctionEmptyArgs", FixFunction)) {} + +void ProBoundsAvoidUncheckedContainerAccess::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ExcludeClasses", + utils::options::serializeStringList(ExcludedClasses)); + Options.store(Opts, "FixMode", FixMode); + Options.store(Opts, "FixFunction", FixFunction); + Options.store(Opts, "FixFunctionEmptyArgs", FixFunctionEmptyArgs); +} + +// TODO: if at() is defined in another class in the class hierarchy of the class +// that defines the operator[] we matched on, findAlternative() will not detect +// it. +static const CXXMethodDecl * +findAlternativeAt(const CXXMethodDecl *MatchedOperator) { + const CXXRecordDecl *Parent = MatchedOperator->getParent(); + const QualType SubscriptThisObjType = + MatchedOperator->getFunctionObjectParameterReferenceType(); + + for (const CXXMethodDecl *Method : Parent->methods()) { + // Require 'Method' to be as accessible as 'MatchedOperator' or more + if (MatchedOperator->getAccess() < Method->getAccess()) + continue; + + if (MatchedOperator->isConst() != Method->isConst()) + continue; + + const QualType AtThisObjType = + Method->getFunctionObjectParameterReferenceType(); + if (SubscriptThisObjType != AtThisObjType) + continue; + + if (!Method->getNameInfo().getName().isIdentifier() || + Method->getName() != "at") + continue; + + const bool SameReturnType = + Method->getReturnType() == MatchedOperator->getReturnType(); + if (!SameReturnType) + continue; + + const bool SameNumberOfArguments = + Method->getNumParams() == MatchedOperator->getNumParams(); + if (!SameNumberOfArguments) + continue; + + for (unsigned ArgInd = 0; ArgInd < Method->getNumParams(); ArgInd++) { + const bool SameArgType = + Method->parameters()[ArgInd]->getOriginalType() == + MatchedOperator->parameters()[ArgInd]->getOriginalType(); + if (!SameArgType) + continue; + } + + return Method; + } + return nullptr; +} + +void ProBoundsAvoidUncheckedContainerAccess::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher( + mapAnyOf(cxxOperatorCallExpr, cxxMemberCallExpr) + .with(callee( + cxxMethodDecl( + hasOverloadedOperatorName("[]"), + anyOf(parameterCountIs(0), parameterCountIs(1)), + unless(matchers::matchesAnyListedName(ExcludedClasses))) + .bind("operator"))) + .bind("caller"), + this); +} + +void ProBoundsAvoidUncheckedContainerAccess::check( + const MatchFinder::MatchResult &Result) { + + const auto *MatchedExpr = Result.Nodes.getNodeAs("caller"); + + if (FixMode == None) { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider bounds-safe alternatives") + << MatchedExpr->getCallee()->getSourceRange(); + return; + } + + if (const auto *OCE = dyn_cast(MatchedExpr)) { + // Case: a[i] + const auto LeftBracket = SourceRange(OCE->getCallee()->getBeginLoc(), + OCE->getCallee()->getBeginLoc()); + const auto RightBracket = + SourceRange(OCE->getOperatorLoc(), OCE->getOperatorLoc()); + + if (FixMode == At) { + // Case: a[i] => a.at(i) + const auto *MatchedOperator = + Result.Nodes.getNodeAs("operator"); + const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator); + + if (!Alternative) { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternatives") + << MatchedExpr->getCallee()->getSourceRange(); + return; + } + + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternative 'at()'") + << MatchedExpr->getCallee()->getSourceRange() + << FixItHint::CreateReplacement(LeftBracket, ".at(") + << FixItHint::CreateReplacement(RightBracket, ")"); + + diag(Alternative->getBeginLoc(), "viable 'at()' is defined here", + DiagnosticIDs::Note) + << Alternative->getNameInfo().getSourceRange(); + + } else if (FixMode == Function) { + // Case: a[i] => f(a, i) + // + // Since C++23, the subscript operator may also be called without an + // argument, which makes the following distinction necessary + const bool EmptySubscript = + MatchedExpr->getDirectCallee()->getNumParams() == 0; + + if (EmptySubscript) { + auto D = diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]'%select{, use safe " + "function '%1() instead|}0") + << FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str() + << MatchedExpr->getCallee()->getSourceRange(); + if (!FixFunctionEmptyArgs.empty()) { + D << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(), + FixFunctionEmptyArgs.str() + "(") + << FixItHint::CreateRemoval(LeftBracket) + << FixItHint::CreateReplacement(RightBracket, ")"); + } + } else { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', use safe function '%0()' instead") + << FixFunction.str() << MatchedExpr->getCallee()->getSourceRange() + << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(), + FixFunction.str() + "(") + << FixItHint::CreateReplacement(LeftBracket, ", ") + << FixItHint::CreateReplacement(RightBracket, ")"); + } + } + } else if (const auto *MCE = dyn_cast(MatchedExpr)) { + // Case: a.operator[](i) or a->operator[](i) + const auto *Callee = dyn_cast(MCE->getCallee()); + + if (FixMode == At) { + // Cases: a.operator[](i) => a.at(i) and a->operator[](i) => a->at(i) + + const auto *MatchedOperator = + Result.Nodes.getNodeAs("operator"); + + const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator); + if (!Alternative) { + diag(Callee->getBeginLoc(), "possibly unsafe 'operator[]', consider " + "bounds-safe alternative 'at()'") + << Callee->getSourceRange(); + return; + } + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternative 'at()'") + << FixItHint::CreateReplacement( + SourceRange(Callee->getMemberLoc(), Callee->getEndLoc()), + "at"); + + diag(Alternative->getBeginLoc(), "viable 'at()' defined here", + DiagnosticIDs::Note) + << Alternative->getNameInfo().getSourceRange(); + + } else if (FixMode == Function) { + // Cases: a.operator[](i) => f(a, i) and a->operator[](i) => f(*a, i) + const auto *Callee = dyn_cast(MCE->getCallee()); + + const bool EmptySubscript = + MCE->getMethodDecl()->getNumNonObjectParams() == 0; + + std::string BeginInsertion = + (EmptySubscript ? FixFunctionEmptyArgs.str() : FixFunction.str()) + + "("; + + if (Callee->isArrow()) + BeginInsertion += "*"; + + // Since C++23, the subscript operator may also be called without an + // argument, which makes the following distinction necessary + if (EmptySubscript) { + auto D = diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]'%select{, use safe " + "function '%1()' instead|}0") + << FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str() + << Callee->getSourceRange(); + + if (!FixFunctionEmptyArgs.empty()) { + D << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(), + BeginInsertion) + << FixItHint::CreateRemoval( + SourceRange(Callee->getOperatorLoc(), + MCE->getRParenLoc().getLocWithOffset(-1))); + } + } else { + diag(Callee->getBeginLoc(), + "possibly unsafe 'operator[]', use safe function '%0()' instead") + << FixFunction.str() << Callee->getSourceRange() + << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(), + BeginInsertion) + << FixItHint::CreateReplacement( + SourceRange( + Callee->getOperatorLoc(), + MCE->getArg(0)->getBeginLoc().getLocWithOffset(-1)), + ", "); + } + } + } +} + +} // namespace clang::tidy::cppcoreguidelines + +namespace clang::tidy { +using P = cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess; + +llvm::ArrayRef> +OptionEnumMapping::getEnumMapping() { + static constexpr std::pair Mapping[] = { + {P::None, "none"}, {P::At, "at"}, {P::Function, "function"}}; + return {Mapping}; +} +} // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.h new file mode 100644 index 0000000000000..cfd52d69c0f58 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.h @@ -0,0 +1,56 @@ +//===--- ProBoundsAvoidUncheckedContainerAccess.h - clang-tidy --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::cppcoreguidelines { + +/// Flags calls to operator[] in STL containers and suggests replacing it with +/// safe alternatives. +/// +/// See +/// https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#slcon3-avoid-bounds-errors +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.html +class ProBoundsAvoidUncheckedContainerAccess : public ClangTidyCheck { +public: + ProBoundsAvoidUncheckedContainerAccess(StringRef Name, + ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + enum FixModes { None, At, Function }; + +private: + // A list of class names that are excluded from the warning + std::vector ExcludedClasses; + // Setting which fix to suggest + FixModes FixMode; + llvm::StringRef FixFunction; + llvm::StringRef FixFunctionEmptyArgs; +}; +} // namespace clang::tidy::cppcoreguidelines + +namespace clang::tidy { +template <> +struct OptionEnumMapping< + cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess::FixModes> { + static ArrayRef> + getEnumMapping(); +}; +} // namespace clang::tidy +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp index 9ac7b9e057e35..51995c5f64ef6 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp @@ -14,6 +14,18 @@ using namespace clang::ast_matchers; namespace clang::tidy::cppcoreguidelines { +ProBoundsPointerArithmeticCheck::ProBoundsPointerArithmeticCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowIncrementDecrementOperators( + Options.get("AllowIncrementDecrementOperators", false)) {} + +void ProBoundsPointerArithmeticCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowIncrementDecrementOperators", + AllowIncrementDecrementOperators); +} + void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { const auto AllPointerTypes = anyOf(hasType(hasUnqualifiedDesugaredType(pointerType())), @@ -30,13 +42,14 @@ void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { this); // Flag all operators ++, -- that result in a pointer - Finder->addMatcher( - unaryOperator(hasAnyOperatorName("++", "--"), - hasType(hasUnqualifiedDesugaredType(pointerType())), - unless(hasUnaryOperand( - ignoringImpCasts(declRefExpr(to(isImplicit())))))) - .bind("expr"), - this); + if (!AllowIncrementDecrementOperators) + Finder->addMatcher( + unaryOperator(hasAnyOperatorName("++", "--"), + hasType(hasUnqualifiedDesugaredType(pointerType())), + unless(hasUnaryOperand( + ignoringImpCasts(declRefExpr(to(isImplicit())))))) + .bind("expr"), + this); // Array subscript on a pointer (not an array) is also pointer arithmetic Finder->addMatcher( diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h index 3466c72a769e9..785f754055fb8 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h @@ -21,13 +21,16 @@ namespace clang::tidy::cppcoreguidelines { /// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/pro-bounds-pointer-arithmetic.html class ProBoundsPointerArithmeticCheck : public ClangTidyCheck { public: - ProBoundsPointerArithmeticCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + ProBoundsPointerArithmeticCheck(StringRef Name, ClangTidyContext *Context); bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus; } void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool AllowIncrementDecrementOperators; }; } // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp index b413b12cd37ab..40607597297b5 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp @@ -190,7 +190,7 @@ struct InitializerInsertion { // Convenience utility to get a RecordDecl from a QualType. const RecordDecl *getCanonicalRecordDecl(const QualType &Type) { if (const auto *RT = Type.getCanonicalType()->getAs()) - return RT->getDecl(); + return RT->getOriginalDecl(); return nullptr; } diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.cpp index 76754394de760..40fd15c08f0a1 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.cpp @@ -92,7 +92,7 @@ void SlicingCheck::diagnoseSlicedOverriddenMethods( for (const auto &Base : DerivedDecl.bases()) { if (const auto *BaseRecordType = Base.getType()->getAs()) { if (const auto *BaseRecord = cast_or_null( - BaseRecordType->getDecl()->getDefinition())) + BaseRecordType->getOriginalDecl()->getDefinition())) diagnoseSlicedOverriddenMethods(Call, *BaseRecord, BaseDecl); } } diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h index 02bfaf1205f40..317547f0a9c87 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h @@ -22,6 +22,9 @@ class SlicingCheck : public ClangTidyCheck { public: SlicingCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; diff --git a/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsCallsCheck.h b/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsCallsCheck.h index 49d7820d04410..120dc90b2cbc0 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsCallsCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsCallsCheck.h @@ -21,6 +21,9 @@ class DefaultArgumentsCallsCheck : public ClangTidyCheck { public: DefaultArgumentsCallsCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsDeclarationsCheck.h b/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsDeclarationsCheck.h index d03d0b7a10f05..da73fa4064cbd 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsDeclarationsCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/DefaultArgumentsDeclarationsCheck.h @@ -21,6 +21,9 @@ class DefaultArgumentsDeclarationsCheck : public ClangTidyCheck { public: DefaultArgumentsDeclarationsCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp index 80ff97a762134..0302a5ad4957c 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp +++ b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp @@ -74,7 +74,7 @@ bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) { const auto *Ty = I.getType()->getAs(); if (!Ty) continue; - const RecordDecl *D = Ty->getDecl()->getDefinition(); + const RecordDecl *D = Ty->getOriginalDecl()->getDefinition(); if (!D) continue; const auto *Base = cast(D); @@ -106,7 +106,8 @@ void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) { const auto *Ty = I.getType()->getAs(); if (!Ty) continue; - const auto *Base = cast(Ty->getDecl()->getDefinition()); + const auto *Base = + cast(Ty->getOriginalDecl()->getDefinition()); if (!isInterface(Base)) NumConcrete++; } @@ -117,7 +118,8 @@ void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) { const auto *Ty = V.getType()->getAs(); if (!Ty) continue; - const auto *Base = cast(Ty->getDecl()->getDefinition()); + const auto *Base = + cast(Ty->getOriginalDecl()->getDefinition()); if (!isInterface(Base)) NumConcrete++; } diff --git a/clang-tools-extra/clang-tidy/fuchsia/OverloadedOperatorCheck.h b/clang-tools-extra/clang-tidy/fuchsia/OverloadedOperatorCheck.h index 007f410a6eac6..d26349d6e9afc 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/OverloadedOperatorCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/OverloadedOperatorCheck.h @@ -21,6 +21,9 @@ class OverloadedOperatorCheck : public ClangTidyCheck { public: OverloadedOperatorCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h index 426d89d046a63..1bdf19f9146fb 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h @@ -21,6 +21,9 @@ class VirtualInheritanceCheck : public ClangTidyCheck { public: VirtualInheritanceCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp index e076b39b5d978..14e11eb0bc697 100644 --- a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp @@ -89,6 +89,30 @@ static StringRef getDestTypeString(const SourceManager &SM, SM, LangOpts); } +static bool sameTypeAsWritten(QualType X, QualType Y) { + if (X.getCanonicalType() != Y.getCanonicalType()) + return false; + + auto TC = X->getTypeClass(); + if (TC != Y->getTypeClass()) + return false; + + switch (TC) { + case Type::Typedef: + return declaresSameEntity(cast(X)->getDecl(), + cast(Y)->getDecl()); + case Type::Pointer: + return sameTypeAsWritten(cast(X)->getPointeeType(), + cast(Y)->getPointeeType()); + case Type::RValueReference: + case Type::LValueReference: + return sameTypeAsWritten(cast(X)->getPointeeType(), + cast(Y)->getPointeeType()); + default: + return true; + } +} + void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { const auto *CastExpr = Result.Nodes.getNodeAs("cast"); @@ -128,12 +152,7 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { // case of overloaded functions, so detection of redundant casts is trickier // in this case. Don't emit "redundant cast" warnings for function // pointer/reference types. - QualType Src = SourceTypeAsWritten, Dst = DestTypeAsWritten; - if (const auto *ElTy = dyn_cast(Src)) - Src = ElTy->getNamedType(); - if (const auto *ElTy = dyn_cast(Dst)) - Dst = ElTy->getNamedType(); - if (Src == Dst) { + if (sameTypeAsWritten(SourceTypeAsWritten, DestTypeAsWritten)) { diag(CastExpr->getBeginLoc(), "redundant cast to the same type") << FixItHint::CreateRemoval(ReplaceRange); return; diff --git a/clang-tools-extra/clang-tidy/google/AvoidUnderscoreInGoogletestNameCheck.h b/clang-tools-extra/clang-tidy/google/AvoidUnderscoreInGoogletestNameCheck.h index baec40e440cbd..b53e6c45913d5 100644 --- a/clang-tools-extra/clang-tidy/google/AvoidUnderscoreInGoogletestNameCheck.h +++ b/clang-tools-extra/clang-tidy/google/AvoidUnderscoreInGoogletestNameCheck.h @@ -21,6 +21,9 @@ namespace clang::tidy::google::readability { class AvoidUnderscoreInGoogletestNameCheck : public ClangTidyCheck { public: using ClangTidyCheck::ClangTidyCheck; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override; diff --git a/clang-tools-extra/clang-tidy/google/DefaultArgumentsCheck.h b/clang-tools-extra/clang-tidy/google/DefaultArgumentsCheck.h index a8f0b0112fb94..49d95a5acd35c 100644 --- a/clang-tools-extra/clang-tidy/google/DefaultArgumentsCheck.h +++ b/clang-tools-extra/clang-tidy/google/DefaultArgumentsCheck.h @@ -23,6 +23,9 @@ class DefaultArgumentsCheck : public ClangTidyCheck { public: DefaultArgumentsCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp index 3deea0620514b..68233ec6bd441 100644 --- a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp @@ -72,7 +72,7 @@ static bool isStdInitializerList(QualType Type) { } if (const auto *RT = Type->getAs()) { if (const auto *Specialization = - dyn_cast(RT->getDecl())) + dyn_cast(RT->getOriginalDecl())) return declIsStdInitializerList(Specialization->getSpecializedTemplate()); } return false; diff --git a/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp b/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp index 805dcaf3ce402..274b8afa98bd6 100644 --- a/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp @@ -257,8 +257,13 @@ getAliasNameRange(const MatchFinder::MatchResult &Result) { return CharSourceRange::getTokenRange( Using->getNameInfo().getSourceRange()); } - return CharSourceRange::getTokenRange( - Result.Nodes.getNodeAs("typeloc")->getSourceRange()); + TypeLoc TL = *Result.Nodes.getNodeAs("typeloc"); + if (auto QTL = TL.getAs()) + TL = QTL.getUnqualifiedLoc(); + + if (auto TTL = TL.getAs()) + return CharSourceRange::getTokenRange(TTL.getNameLoc()); + return CharSourceRange::getTokenRange(TL.castAs().getNameLoc()); } void UpgradeGoogletestCaseCheck::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/hicpp/IgnoredRemoveResultCheck.h b/clang-tools-extra/clang-tidy/hicpp/IgnoredRemoveResultCheck.h index 48354c34a8581..39c45fea9aae4 100644 --- a/clang-tools-extra/clang-tidy/hicpp/IgnoredRemoveResultCheck.h +++ b/clang-tools-extra/clang-tidy/hicpp/IgnoredRemoveResultCheck.h @@ -21,6 +21,9 @@ namespace clang::tidy::hicpp { class IgnoredRemoveResultCheck : public bugprone::UnusedReturnValueCheck { public: IgnoredRemoveResultCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void storeOptions(ClangTidyOptions::OptionMap &Opts) override; }; diff --git a/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.h b/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.h index 1099ab0cd0e44..07e018a6fc969 100644 --- a/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/PreferRegisterOverUnsignedCheck.h @@ -23,6 +23,9 @@ class PreferRegisterOverUnsignedCheck : public ClangTidyCheck { public: PreferRegisterOverUnsignedCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h b/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h index e1f25e530289a..b4550ecb226bf 100644 --- a/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/TwineLocalCheck.h @@ -19,6 +19,9 @@ class TwineLocalCheck : public ClangTidyCheck { public: TwineLocalCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt index fd7affd22a463..2cfee5fd10713 100644 --- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt @@ -32,6 +32,7 @@ add_clang_library(clangTidyMiscModule STATIC NoRecursionCheck.cpp NonCopyableObjects.cpp NonPrivateMemberVariablesInClassesCheck.cpp + OverrideWithDifferentVisibilityCheck.cpp RedundantExpressionCheck.cpp StaticAssertCheck.cpp ThrowByValueCatchByReferenceCheck.cpp diff --git a/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp b/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp index 697398a54332d..b32507d66cbac 100644 --- a/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp @@ -98,11 +98,12 @@ void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) { hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))), hasType(referenceType(pointee(substTemplateTypeParmType())))); - const auto AllowedType = hasType(qualType(anyOf( - hasDeclaration(namedDecl(matchers::matchesAnyListedName(AllowedTypes))), - references(namedDecl(matchers::matchesAnyListedName(AllowedTypes))), - pointerType(pointee(hasDeclaration( - namedDecl(matchers::matchesAnyListedName(AllowedTypes)))))))); + auto AllowedTypeDecl = namedDecl( + anyOf(matchers::matchesAnyListedName(AllowedTypes), usingShadowDecl())); + + const auto AllowedType = hasType(qualType( + anyOf(hasDeclaration(AllowedTypeDecl), references(AllowedTypeDecl), + pointerType(pointee(hasDeclaration(AllowedTypeDecl)))))); const auto AutoTemplateType = varDecl( anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))), diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp index 6ddebcbc0e152..f675ca70deb9d 100644 --- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp @@ -22,6 +22,7 @@ #include "NoRecursionCheck.h" #include "NonCopyableObjects.h" #include "NonPrivateMemberVariablesInClassesCheck.h" +#include "OverrideWithDifferentVisibilityCheck.h" #include "RedundantExpressionCheck.h" #include "StaticAssertCheck.h" #include "ThrowByValueCatchByReferenceCheck.h" @@ -81,6 +82,8 @@ class MiscModule : public ClangTidyModule { "misc-use-anonymous-namespace"); CheckFactories.registerCheck( "misc-use-internal-linkage"); + CheckFactories.registerCheck( + "misc-override-with-different-visibility"); } }; diff --git a/clang-tools-extra/clang-tidy/misc/MisplacedConstCheck.cpp b/clang-tools-extra/clang-tidy/misc/MisplacedConstCheck.cpp index 0cdd48c13b2a6..bb64a5618620c 100644 --- a/clang-tools-extra/clang-tidy/misc/MisplacedConstCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/MisplacedConstCheck.cpp @@ -19,13 +19,13 @@ void MisplacedConstCheck::registerMatchers(MatchFinder *Finder) { pointee(anyOf(isConstQualified(), ignoringParens(functionType())))))); Finder->addMatcher( - valueDecl(hasType(qualType( - isConstQualified(), - elaboratedType(namesType(typedefType(hasDeclaration( - anyOf(typedefDecl(NonConstAndNonFunctionPointerType) - .bind("typedef"), - typeAliasDecl(NonConstAndNonFunctionPointerType) - .bind("typeAlias"))))))))) + valueDecl( + hasType(qualType(isConstQualified(), + typedefType(hasDeclaration(anyOf( + typedefDecl(NonConstAndNonFunctionPointerType) + .bind("typedef"), + typeAliasDecl(NonConstAndNonFunctionPointerType) + .bind("typeAlias"))))))) .bind("decl"), this); } diff --git a/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp b/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp index 40808aaf7c3da..2837f40bc49b8 100644 --- a/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp @@ -59,7 +59,9 @@ AST_MATCHER(FunctionDecl, isPlacementOverload) { return true; } -OverloadedOperatorKind getCorrespondingOverload(const FunctionDecl *FD) { +} // namespace + +static OverloadedOperatorKind getCorrespondingOverload(const FunctionDecl *FD) { switch (FD->getOverloadedOperator()) { default: break; @@ -75,7 +77,7 @@ OverloadedOperatorKind getCorrespondingOverload(const FunctionDecl *FD) { llvm_unreachable("Not an overloaded allocation operator"); } -const char *getOperatorName(OverloadedOperatorKind K) { +static const char *getOperatorName(OverloadedOperatorKind K) { switch (K) { default: break; @@ -91,13 +93,14 @@ const char *getOperatorName(OverloadedOperatorKind K) { llvm_unreachable("Not an overloaded allocation operator"); } -bool areCorrespondingOverloads(const FunctionDecl *LHS, - const FunctionDecl *RHS) { +static bool areCorrespondingOverloads(const FunctionDecl *LHS, + const FunctionDecl *RHS) { return RHS->getOverloadedOperator() == getCorrespondingOverload(LHS); } -bool hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD, - const CXXRecordDecl *RD = nullptr) { +static bool +hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD, + const CXXRecordDecl *RD = nullptr) { if (RD) { // Check the methods in the given class and accessible to derived classes. for (const auto *BMD : RD->methods()) @@ -124,8 +127,6 @@ bool hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD, return false; } -} // anonymous namespace - void NewDeleteOverloadsCheck::registerMatchers(MatchFinder *Finder) { // Match all operator new and operator delete overloads (including the array // forms). Do not match implicit operators, placement operators, or diff --git a/clang-tools-extra/clang-tidy/misc/OverrideWithDifferentVisibilityCheck.cpp b/clang-tools-extra/clang-tidy/misc/OverrideWithDifferentVisibilityCheck.cpp new file mode 100644 index 0000000000000..12f78affe463e --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/OverrideWithDifferentVisibilityCheck.cpp @@ -0,0 +1,150 @@ +//===--- OverrideWithDifferentVisibilityCheck.cpp - clang-tidy ------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "OverrideWithDifferentVisibilityCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; +using namespace clang; + +namespace { + +AST_MATCHER(NamedDecl, isOperatorDecl) { + DeclarationName::NameKind const NK = Node.getDeclName().getNameKind(); + return NK != DeclarationName::Identifier && + NK != DeclarationName::CXXConstructorName && + NK != DeclarationName::CXXDestructorName; +} + +} // namespace + +namespace clang::tidy { + +template <> +struct OptionEnumMapping< + misc::OverrideWithDifferentVisibilityCheck::ChangeKind> { + static llvm::ArrayRef> + getEnumMapping() { + static constexpr std::pair< + misc::OverrideWithDifferentVisibilityCheck::ChangeKind, StringRef> + Mapping[] = { + {misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Any, + "any"}, + {misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Widening, + "widening"}, + {misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Narrowing, + "narrowing"}, + }; + return {Mapping}; + } +}; + +namespace misc { + +OverrideWithDifferentVisibilityCheck::OverrideWithDifferentVisibilityCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + DetectVisibilityChange( + Options.get("DisallowedVisibilityChange", ChangeKind::Any)), + CheckDestructors(Options.get("CheckDestructors", false)), + CheckOperators(Options.get("CheckOperators", false)), + IgnoredFunctions(utils::options::parseStringList( + Options.get("IgnoredFunctions", ""))) {} + +void OverrideWithDifferentVisibilityCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "DisallowedVisibilityChange", DetectVisibilityChange); + Options.store(Opts, "CheckDestructors", CheckDestructors); + Options.store(Opts, "CheckOperators", CheckOperators); + Options.store(Opts, "IgnoredFunctions", + utils::options::serializeStringList(IgnoredFunctions)); +} + +void OverrideWithDifferentVisibilityCheck::registerMatchers( + MatchFinder *Finder) { + const auto IgnoredDecl = + namedDecl(matchers::matchesAnyListedName(IgnoredFunctions)); + const auto FilterDestructors = + CheckDestructors ? decl() : decl(unless(cxxDestructorDecl())); + const auto FilterOperators = + CheckOperators ? namedDecl() : namedDecl(unless(isOperatorDecl())); + Finder->addMatcher( + cxxMethodDecl( + isVirtual(), FilterDestructors, FilterOperators, + ofClass( + cxxRecordDecl(unless(isExpansionInSystemHeader())).bind("class")), + forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base")), + unless(IgnoredDecl)) + .bind("base_func"))) + .bind("func"), + this); +} + +void OverrideWithDifferentVisibilityCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *const MatchedFunction = + Result.Nodes.getNodeAs("func"); + if (!MatchedFunction->isCanonicalDecl()) + return; + + const auto *const ParentClass = + Result.Nodes.getNodeAs("class"); + const auto *const BaseClass = Result.Nodes.getNodeAs("base"); + CXXBasePaths Paths; + if (!ParentClass->isDerivedFrom(BaseClass, Paths)) + return; + + const auto *const OverriddenFunction = + Result.Nodes.getNodeAs("base_func"); + AccessSpecifier const ActualAccess = MatchedFunction->getAccess(); + AccessSpecifier OverriddenAccess = OverriddenFunction->getAccess(); + + const CXXBaseSpecifier *InheritanceWithStrictVisibility = nullptr; + for (const CXXBasePath &Path : Paths) { + for (const CXXBasePathElement &Elem : Path) { + if (Elem.Base->getAccessSpecifier() > OverriddenAccess) { + OverriddenAccess = Elem.Base->getAccessSpecifier(); + InheritanceWithStrictVisibility = Elem.Base; + } + } + } + + if (ActualAccess != OverriddenAccess) { + if (DetectVisibilityChange == ChangeKind::Widening && + ActualAccess > OverriddenAccess) + return; + if (DetectVisibilityChange == ChangeKind::Narrowing && + ActualAccess < OverriddenAccess) + return; + + if (InheritanceWithStrictVisibility) { + diag(MatchedFunction->getLocation(), + "visibility of function %0 is changed from %1 (through %1 " + "inheritance of class %2) to %3") + << MatchedFunction << OverriddenAccess + << InheritanceWithStrictVisibility->getType() << ActualAccess; + diag(InheritanceWithStrictVisibility->getBeginLoc(), + "%0 is inherited as %1 here", DiagnosticIDs::Note) + << InheritanceWithStrictVisibility->getType() << OverriddenAccess; + } else { + diag(MatchedFunction->getLocation(), + "visibility of function %0 is changed from %1 in class %2 to %3") + << MatchedFunction << OverriddenAccess << BaseClass << ActualAccess; + } + diag(OverriddenFunction->getLocation(), "function declared here as %0", + DiagnosticIDs::Note) + << OverriddenFunction->getAccess(); + } +} + +} // namespace misc + +} // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/misc/OverrideWithDifferentVisibilityCheck.h b/clang-tools-extra/clang-tidy/misc/OverrideWithDifferentVisibilityCheck.h new file mode 100644 index 0000000000000..1f5222d99196b --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/OverrideWithDifferentVisibilityCheck.h @@ -0,0 +1,43 @@ +//===--- OverrideWithDifferentVisibilityCheck.h - clang-tidy --*- C++ -*---===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OVERRIDEWITHDIFFERENTVISIBILITYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OVERRIDEWITHDIFFERENTVISIBILITYCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::misc { + +/// Finds virtual function overrides with different visibility than the function +/// in the base class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc/override-with-different-visibility.html +class OverrideWithDifferentVisibilityCheck : public ClangTidyCheck { +public: + enum class ChangeKind { Any, Widening, Narrowing }; + + OverrideWithDifferentVisibilityCheck(StringRef Name, + ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + +private: + ChangeKind DetectVisibilityChange; + bool CheckDestructors; + bool CheckOperators; + std::vector IgnoredFunctions; +}; + +} // namespace clang::tidy::misc + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OVERRIDEWITHDIFFERENTVISIBILITYCHECK_H diff --git a/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp b/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp index 9b2af2a8ca7d8..107eda2e98f27 100644 --- a/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp @@ -45,14 +45,6 @@ static bool incrementWithoutOverflow(const APSInt &Value, APSInt &Result) { return Value < Result; } -static bool areEquivalentNameSpecifier(const NestedNameSpecifier *Left, - const NestedNameSpecifier *Right) { - llvm::FoldingSetNodeID LeftID, RightID; - Left->Profile(LeftID); - Right->Profile(RightID); - return LeftID == RightID; -} - static bool areEquivalentExpr(const Expr *Left, const Expr *Right) { if (!Left || !Right) return !Left && !Right; @@ -104,9 +96,8 @@ static bool areEquivalentExpr(const Expr *Left, const Expr *Right) { if (cast(Left)->getDeclName() != cast(Right)->getDeclName()) return false; - return areEquivalentNameSpecifier( - cast(Left)->getQualifier(), - cast(Right)->getQualifier()); + return cast(Left)->getQualifier() == + cast(Right)->getQualifier(); case Stmt::DeclRefExprClass: return cast(Left)->getDecl() == cast(Right)->getDecl(); diff --git a/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp b/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp index 3fdaf9239f6af..8200239b982a0 100644 --- a/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp @@ -29,11 +29,13 @@ void UnconventionalAssignOperatorCheck::registerMatchers( const auto HasGoodReturnType = cxxMethodDecl(returns(hasCanonicalType(lValueReferenceType(pointee( unless(isConstQualified()), - anyOf(autoType(), hasDeclaration(equalsBoundNode("class")))))))); + anyOf(autoType(), + hasDeclaration(declaresSameEntityAsBoundNode("class")))))))); const auto IsSelf = qualType(hasCanonicalType( - anyOf(hasDeclaration(equalsBoundNode("class")), - referenceType(pointee(hasDeclaration(equalsBoundNode("class"))))))); + anyOf(hasDeclaration(declaresSameEntityAsBoundNode("class")), + referenceType(pointee( + hasDeclaration(declaresSameEntityAsBoundNode("class"))))))); const auto IsAssign = cxxMethodDecl(unless(anyOf(isDeleted(), isPrivate(), isImplicit())), hasName("operator="), ofClass(recordDecl().bind("class"))) diff --git a/clang-tools-extra/clang-tidy/misc/UnusedAliasDeclsCheck.cpp b/clang-tools-extra/clang-tidy/misc/UnusedAliasDeclsCheck.cpp index 86992cd8a141b..4fa679aa8dd88 100644 --- a/clang-tools-extra/clang-tidy/misc/UnusedAliasDeclsCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/UnusedAliasDeclsCheck.cpp @@ -35,12 +35,12 @@ void UnusedAliasDeclsCheck::check(const MatchFinder::MatchResult &Result) { } if (const auto *NestedName = - Result.Nodes.getNodeAs("nns")) { - if (const auto *AliasDecl = dyn_cast_if_present( - NestedName->getAsNamespace())) { + Result.Nodes.getNodeAs("nns"); + NestedName && + NestedName->getKind() == NestedNameSpecifier::Kind::Namespace) + if (const auto *AliasDecl = dyn_cast( + NestedName->getAsNamespaceAndPrefix().Namespace)) FoundDecls[AliasDecl] = CharSourceRange(); - } - } } void UnusedAliasDeclsCheck::onEndOfTranslationUnit() { diff --git a/clang-tools-extra/clang-tidy/misc/UnusedUsingDeclsCheck.cpp b/clang-tools-extra/clang-tidy/misc/UnusedUsingDeclsCheck.cpp index d5c5fa3364d63..8211a0ec6a5e1 100644 --- a/clang-tools-extra/clang-tidy/misc/UnusedUsingDeclsCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/UnusedUsingDeclsCheck.cpp @@ -71,11 +71,7 @@ void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) { templateArgument().bind("used")))), this); Finder->addMatcher(userDefinedLiteral().bind("used"), this); - Finder->addMatcher( - loc(elaboratedType(unless(hasQualifier(nestedNameSpecifier())), - hasUnqualifiedDesugaredType( - type(asTagDecl(tagDecl().bind("used")))))), - this); + Finder->addMatcher(loc(asTagDecl(tagDecl().bind("used"))), this); // Cases where we can identify the UsingShadowDecl directly, rather than // just its target. // FIXME: cover more cases in this way, as the AST supports it. @@ -136,7 +132,7 @@ void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) { } if (const auto *ECD = dyn_cast(Used)) { if (const auto *ET = ECD->getType()->getAs()) - removeFromFoundDecls(ET->getDecl()); + removeFromFoundDecls(ET->getOriginalDecl()); } }; // We rely on the fact that the clang AST is walked in order, usages are only diff --git a/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.cpp b/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.cpp index f22f48d831608..2aca61021166d 100644 --- a/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.cpp @@ -29,8 +29,7 @@ static std::optional getReplacementType(StringRef Type) { void DeprecatedIosBaseAliasesCheck::registerMatchers(MatchFinder *Finder) { auto IoStateDecl = typedefDecl(hasAnyName(DeprecatedTypes)).bind("TypeDecl"); - auto IoStateType = - qualType(hasDeclaration(IoStateDecl), unless(elaboratedType())); + auto IoStateType = typedefType(hasDeclaration(IoStateDecl)); Finder->addMatcher(typeLoc(loc(IoStateType)).bind("TypeLoc"), this); } @@ -43,12 +42,14 @@ void DeprecatedIosBaseAliasesCheck::check( StringRef TypeName = Typedef->getName(); auto Replacement = getReplacementType(TypeName); - const auto *TL = Result.Nodes.getNodeAs("TypeLoc"); - SourceLocation IoStateLoc = TL->getBeginLoc(); + TypeLoc TL = *Result.Nodes.getNodeAs("TypeLoc"); + if (auto QTL = TL.getAs()) + TL = QTL.getUnqualifiedLoc(); + SourceLocation IoStateLoc = TL.castAs().getNameLoc(); // Do not generate fixits for matches depending on template arguments and // macro expansions. - bool Fix = Replacement && !TL->getType()->isDependentType(); + bool Fix = Replacement && !TL.getType()->isDependentType(); if (IoStateLoc.isMacroID()) { IoStateLoc = SM.getSpellingLoc(IoStateLoc); Fix = false; diff --git a/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp index c2db858f72e32..118e96a6f34ae 100644 --- a/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp @@ -395,16 +395,12 @@ void MacroToEnumCallbacks::Endif(SourceLocation Loc, SourceLocation IfLoc) { --CurrentFile->ConditionScopes; } -namespace { - template -bool textEquals(const char (&Needle)[N], const char *HayStack) { +static bool textEquals(const char (&Needle)[N], const char *HayStack) { return StringRef{HayStack, N - 1} == Needle; } -template size_t len(const char (&)[N]) { return N - 1; } - -} // namespace +template static size_t len(const char (&)[N]) { return N - 1; } void MacroToEnumCallbacks::PragmaDirective(SourceLocation Loc, PragmaIntroducerKind Introducer) { diff --git a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp index deef3586628c6..cea48ce6f4564 100644 --- a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -16,14 +16,13 @@ using namespace clang::ast_matchers; namespace clang::tidy::modernize { -namespace { +static constexpr char ConstructorCall[] = "constructorCall"; +static constexpr char ResetCall[] = "resetCall"; +static constexpr char NewExpression[] = "newExpression"; -constexpr char ConstructorCall[] = "constructorCall"; -constexpr char ResetCall[] = "resetCall"; -constexpr char NewExpression[] = "newExpression"; - -std::string getNewExprName(const CXXNewExpr *NewExpr, const SourceManager &SM, - const LangOptions &Lang) { +static std::string getNewExprName(const CXXNewExpr *NewExpr, + const SourceManager &SM, + const LangOptions &Lang) { StringRef WrittenName = Lexer::getSourceText( CharSourceRange::getTokenRange( NewExpr->getAllocatedTypeSourceInfo()->getTypeLoc().getSourceRange()), @@ -34,8 +33,6 @@ std::string getNewExprName(const CXXNewExpr *NewExpr, const SourceManager &SM, return WrittenName.str(); } -} // namespace - const char MakeSmartPtrCheck::PointerType[] = "pointerType"; MakeSmartPtrCheck::MakeSmartPtrCheck(StringRef Name, ClangTidyContext *Context, diff --git a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp index 1e271dfa768ce..a54d0721a5b7d 100644 --- a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp @@ -77,8 +77,7 @@ AST_MATCHER_P(CXXRecordDecl, isMoveConstructibleInBoundCXXRecordDecl, StringRef, static TypeMatcher notTemplateSpecConstRefType() { return lValueReferenceType( - pointee(unless(elaboratedType(namesType(templateSpecializationType()))), - isConstQualified())); + pointee(unless(templateSpecializationType()), isConstQualified())); } static TypeMatcher nonConstValueType() { diff --git a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp index 24674a407cb36..0c9e909fea7f9 100644 --- a/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/RawStringLiteralCheck.cpp @@ -19,9 +19,7 @@ using namespace clang::ast_matchers; namespace clang::tidy::modernize { -namespace { - -bool containsEscapes(StringRef HayStack, StringRef Escapes) { +static bool containsEscapes(StringRef HayStack, StringRef Escapes) { size_t BackSlash = HayStack.find('\\'); if (BackSlash == StringRef::npos) return false; @@ -35,16 +33,16 @@ bool containsEscapes(StringRef HayStack, StringRef Escapes) { return true; } -bool isRawStringLiteral(StringRef Text) { +static bool isRawStringLiteral(StringRef Text) { // Already a raw string literal if R comes before ". const size_t QuotePos = Text.find('"'); assert(QuotePos != StringRef::npos); return (QuotePos > 0) && (Text[QuotePos - 1] == 'R'); } -bool containsEscapedCharacters(const MatchFinder::MatchResult &Result, - const StringLiteral *Literal, - const CharsBitSet &DisallowedChars) { +static bool containsEscapedCharacters(const MatchFinder::MatchResult &Result, + const StringLiteral *Literal, + const CharsBitSet &DisallowedChars) { // FIXME: Handle L"", u8"", u"" and U"" literals. if (!Literal->isOrdinary()) return false; @@ -64,14 +62,12 @@ bool containsEscapedCharacters(const MatchFinder::MatchResult &Result, return containsEscapes(Text, R"('\"?x01)"); } -bool containsDelimiter(StringRef Bytes, const std::string &Delimiter) { +static bool containsDelimiter(StringRef Bytes, const std::string &Delimiter) { return Bytes.find(Delimiter.empty() ? std::string(R"lit()")lit") : (")" + Delimiter + R"(")")) != StringRef::npos; } -} // namespace - RawStringLiteralCheck::RawStringLiteralCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp index 1ad31d315dc2a..f2142b810a126 100644 --- a/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -48,7 +48,7 @@ void ReplaceAutoPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { void ReplaceAutoPtrCheck::registerMatchers(MatchFinder *Finder) { auto AutoPtrDecl = recordDecl(hasName("auto_ptr"), isInStdNamespace()); - auto AutoPtrType = qualType(hasDeclaration(AutoPtrDecl)); + auto AutoPtrType = hasCanonicalType(recordType(hasDeclaration(AutoPtrDecl))); // std::auto_ptr a; // ^~~~~~~~~~~~~ @@ -58,11 +58,7 @@ void ReplaceAutoPtrCheck::registerMatchers(MatchFinder *Finder) { // // std::auto_ptr fn(std::auto_ptr); // ^~~~~~~~~~~~~ ^~~~~~~~~~~~~ - Finder->addMatcher(typeLoc(loc(qualType(AutoPtrType, - // Skip elaboratedType() as the named - // type will match soon thereafter. - unless(elaboratedType())))) - .bind(AutoPtrTokenId), + Finder->addMatcher(typeLoc(loc(qualType(AutoPtrType))).bind(AutoPtrTokenId), this); // using std::auto_ptr; @@ -118,10 +114,13 @@ void ReplaceAutoPtrCheck::check(const MatchFinder::MatchResult &Result) { } SourceLocation AutoPtrLoc; - if (const auto *TL = Result.Nodes.getNodeAs(AutoPtrTokenId)) { + if (const auto *PTL = Result.Nodes.getNodeAs(AutoPtrTokenId)) { + auto TL = *PTL; + if (auto QTL = TL.getAs()) + TL = QTL.getUnqualifiedLoc(); // std::auto_ptr i; // ^ - if (auto Loc = TL->getAs()) + if (auto Loc = TL.getAs()) AutoPtrLoc = Loc.getTemplateNameLoc(); } else if (const auto *D = Result.Nodes.getNodeAs(AutoPtrTokenId)) { diff --git a/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp index ff0b3213cb58f..472128201acc2 100644 --- a/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp @@ -168,19 +168,6 @@ static DeclarationName getName(const DeclRefExpr &D) { return D.getDecl()->getDeclName(); } -static bool isNamedType(const ElaboratedTypeLoc &ETL) { - if (const auto *TFT = - ETL.getNamedTypeLoc().getTypePtr()->getAs()) { - const TypedefNameDecl *Decl = TFT->getDecl(); - return Decl->getDeclName().isIdentifier() && Decl->getName() == "type"; - } - return false; -} - -static bool isNamedType(const DependentNameTypeLoc &DTL) { - return DTL.getTypePtr()->getIdentifier()->getName() == "type"; -} - namespace { AST_POLYMORPHIC_MATCHER(isValue, AST_POLYMORPHIC_SUPPORTED_TYPES( DeclRefExpr, DependentScopeDeclRefExpr)) { @@ -188,25 +175,20 @@ AST_POLYMORPHIC_MATCHER(isValue, AST_POLYMORPHIC_SUPPORTED_TYPES( return Ident && Ident->isStr("value"); } -AST_POLYMORPHIC_MATCHER(isType, - AST_POLYMORPHIC_SUPPORTED_TYPES(ElaboratedTypeLoc, - DependentNameTypeLoc)) { - return Node.getBeginLoc().isValid() && isNamedType(Node); +AST_MATCHER(TypeLoc, isType) { + if (auto TL = Node.getAs()) { + const auto *TD = TL.getDecl(); + return TD->getDeclName().isIdentifier() && TD->getName() == "type"; + } + if (auto TL = Node.getAs()) + return TL.getTypePtr()->getIdentifier()->getName() == "type"; + return false; } } // namespace static constexpr char Bind[] = ""; void TypeTraitsCheck::registerMatchers(MatchFinder *Finder) { - const ast_matchers::internal::VariadicDynCastAllOfMatcher< - Stmt, - DependentScopeDeclRefExpr> - dependentScopeDeclRefExpr; // NOLINT(readability-identifier-naming) - const ast_matchers::internal::VariadicDynCastAllOfMatcher< - TypeLoc, - DependentNameTypeLoc> - dependentNameTypeLoc; // NOLINT(readability-identifier-naming) - // Only register matchers for trait<...>::value in c++17 mode. if (getLangOpts().CPlusPlus17) { Finder->addMatcher(mapAnyOf(declRefExpr, dependentScopeDeclRefExpr) @@ -214,10 +196,7 @@ void TypeTraitsCheck::registerMatchers(MatchFinder *Finder) { .bind(Bind), this); } - Finder->addMatcher(mapAnyOf(elaboratedTypeLoc, dependentNameTypeLoc) - .with(isType()) - .bind(Bind), - this); + Finder->addMatcher(typeLoc(isType()).bind(Bind), this); } static bool isNamedDeclInStdTraitsSet(const NamedDecl *ND, @@ -226,14 +205,11 @@ static bool isNamedDeclInStdTraitsSet(const NamedDecl *ND, Set.contains(ND->getName()); } -static bool checkTemplatedDecl(const NestedNameSpecifier *NNS, +static bool checkTemplatedDecl(NestedNameSpecifier NNS, const llvm::StringSet<> &Set) { - if (!NNS) + if (NNS.getKind() != NestedNameSpecifier::Kind::Type) return false; - const Type *NNST = NNS->getAsType(); - if (!NNST) - return false; - const auto *TST = NNST->getAs(); + const auto *TST = NNS.getAsType()->getAs(); if (!TST) return false; if (const TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl()) { @@ -250,8 +226,8 @@ void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) { auto EmitValueWarning = [this, &Result](const NestedNameSpecifierLoc &QualLoc, SourceLocation EndLoc) { SourceLocation TemplateNameEndLoc; - if (auto TSTL = QualLoc.getTypeLoc().getAs(); - !TSTL.isNull()) + if (auto TSTL = + QualLoc.getAsTypeLoc().getAs()) TemplateNameEndLoc = Lexer::getLocForEndOfToken( TSTL.getTemplateNameLoc(), 0, *Result.SourceManager, Result.Context->getLangOpts()); @@ -274,8 +250,8 @@ void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) { SourceLocation EndLoc, SourceLocation TypenameLoc) { SourceLocation TemplateNameEndLoc; - if (auto TSTL = QualLoc.getTypeLoc().getAs(); - !TSTL.isNull()) + if (auto TSTL = + QualLoc.getAsTypeLoc().getAs()) TemplateNameEndLoc = Lexer::getLocForEndOfToken( TSTL.getTemplateNameLoc(), 0, *Result.SourceManager, Result.Context->getLangOpts()); @@ -301,23 +277,21 @@ void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) { if (!DRE->hasQualifier()) return; if (const auto *CTSD = dyn_cast_if_present( - DRE->getQualifier()->getAsRecordDecl())) { + DRE->getQualifier().getAsRecordDecl())) { if (isNamedDeclInStdTraitsSet(CTSD, ValueTraits)) EmitValueWarning(DRE->getQualifierLoc(), DRE->getEndLoc()); } return; } - if (const auto *ETL = Result.Nodes.getNodeAs(Bind)) { - const NestedNameSpecifierLoc QualLoc = ETL->getQualifierLoc(); - const auto *NNS = QualLoc.getNestedNameSpecifier(); - if (!NNS) - return; + if (const auto *TL = Result.Nodes.getNodeAs(Bind)) { + const NestedNameSpecifierLoc QualLoc = TL->getQualifierLoc(); + NestedNameSpecifier NNS = QualLoc.getNestedNameSpecifier(); if (const auto *CTSD = dyn_cast_if_present( - NNS->getAsRecordDecl())) { + NNS.getAsRecordDecl())) { if (isNamedDeclInStdTraitsSet(CTSD, TypeTraits)) - EmitTypeWarning(ETL->getQualifierLoc(), ETL->getEndLoc(), - ETL->getElaboratedKeywordLoc()); + EmitTypeWarning(TL->getQualifierLoc(), TL->getEndLoc(), + TL->getElaboratedKeywordLoc()); } return; } diff --git a/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp index f4b63087b7234..b601620633cee 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp @@ -186,16 +186,14 @@ TypeMatcher nestedIterator() { /// declarations and which name standard iterators for standard containers. TypeMatcher iteratorFromUsingDeclaration() { auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName())); - // Types resulting from using declarations are represented by elaboratedType. - return elaboratedType( - // Unwrap the nested name specifier to test for one of the standard - // containers. - hasQualifier(specifiesType(templateSpecializationType(hasDeclaration( - namedDecl(hasStdContainerName(), isInStdNamespace()))))), - // the named type is what comes after the final '::' in the type. It - // should name one of the standard iterator names. - namesType( - anyOf(typedefType(HasIteratorDecl), recordType(HasIteratorDecl)))); + // Unwrap the nested name specifier to test for one of the standard + // containers. + auto Qualifier = hasQualifier(specifiesType(templateSpecializationType( + hasDeclaration(namedDecl(hasStdContainerName(), isInStdNamespace()))))); + // the named type is what comes after the final '::' in the type. It should + // name one of the standard iterator names. + return anyOf(typedefType(HasIteratorDecl, Qualifier), + recordType(HasIteratorDecl, Qualifier)); } /// This matcher returns declaration statements that contain variable diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp index e9b96c4016af6..c4a64be537a44 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp @@ -8,6 +8,7 @@ #include "UseConstraintsCheck.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" @@ -60,9 +61,11 @@ matchEnableIfSpecializationImplTypename(TypeLoc TheType) { Keyword != ElaboratedTypeKeyword::None)) { return std::nullopt; } - TheType = Dep.getQualifierLoc().getTypeLoc(); + TheType = Dep.getQualifierLoc().getAsTypeLoc(); if (TheType.isNull()) return std::nullopt; + } else { + return std::nullopt; } if (const auto SpecializationLoc = @@ -78,6 +81,13 @@ matchEnableIfSpecializationImplTypename(TypeLoc TheType) { if (!TD || TD->getName() != "enable_if") return std::nullopt; + assert(!TD->getTemplateParameters()->empty() && + "found template with no template parameters?"); + const auto *FirstParam = dyn_cast( + TD->getTemplateParameters()->getParam(0)); + if (!FirstParam || !FirstParam->getType()->isBooleanType()) + return std::nullopt; + int NumArgs = SpecializationLoc.getNumArgs(); if (NumArgs != 1 && NumArgs != 2) return std::nullopt; @@ -89,9 +99,6 @@ matchEnableIfSpecializationImplTypename(TypeLoc TheType) { static std::optional matchEnableIfSpecializationImplTrait(TypeLoc TheType) { - if (const auto Elaborated = TheType.getAs()) - TheType = Elaborated.getNamedTypeLoc(); - if (const auto SpecializationLoc = TheType.getAs()) { @@ -108,6 +115,13 @@ matchEnableIfSpecializationImplTrait(TypeLoc TheType) { if (!Specialization->isTypeAlias()) return std::nullopt; + assert(!TD->getTemplateParameters()->empty() && + "found template with no template parameters?"); + const auto *FirstParam = dyn_cast( + TD->getTemplateParameters()->getParam(0)); + if (!FirstParam || !FirstParam->getType()->isBooleanType()) + return std::nullopt; + if (const auto *AliasedType = dyn_cast(Specialization->getAliasedType())) { ElaboratedTypeKeyword Keyword = AliasedType->getKeyword(); diff --git a/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp index aaf24eaa33c1b..ee49d8a7cb0b0 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp @@ -164,10 +164,10 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) { auto CallEmplacy = cxxMemberCallExpr( hasDeclaration( functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))), - on(hasTypeOrPointeeType(hasCanonicalType(hasDeclaration( - has(typedefNameDecl(hasName("value_type"), - hasType(type(hasUnqualifiedDesugaredType( - recordType().bind("value_type"))))))))))); + on(hasTypeOrPointeeType( + hasCanonicalType(hasDeclaration(has(typedefNameDecl( + hasName("value_type"), + hasType(hasCanonicalType(recordType().bind("value_type")))))))))); // We can't replace push_backs of smart pointer because // if emplacement fails (f.e. bad_alloc in vector) we will have leak of @@ -241,17 +241,16 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) { auto HasConstructExprWithValueTypeType = has(ignoringImplicit(cxxConstructExpr( - SoughtConstructExpr, hasType(type(hasUnqualifiedDesugaredType( - type(equalsBoundNode("value_type")))))))); - - auto HasBracedInitListWithValueTypeType = - anyOf(allOf(HasConstructInitListExpr, - has(initListExpr(hasType(type(hasUnqualifiedDesugaredType( - type(equalsBoundNode("value_type")))))))), - has(cxxBindTemporaryExpr( - HasConstructInitListExpr, - has(initListExpr(hasType(type(hasUnqualifiedDesugaredType( - type(equalsBoundNode("value_type")))))))))); + SoughtConstructExpr, + hasType(hasCanonicalType(type(equalsBoundNode("value_type"))))))); + + auto HasBracedInitListWithValueTypeType = anyOf( + allOf(HasConstructInitListExpr, + has(initListExpr(hasType( + hasCanonicalType(type(equalsBoundNode("value_type"))))))), + has(cxxBindTemporaryExpr(HasConstructInitListExpr, + has(initListExpr(hasType(hasCanonicalType( + type(equalsBoundNode("value_type"))))))))); auto HasConstructExprWithValueTypeTypeAsLastArgument = hasLastArgument( materializeTemporaryExpr( @@ -289,19 +288,17 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) { this); Finder->addMatcher( - traverse( - TK_AsIs, - cxxMemberCallExpr( - CallEmplacy, - on(hasType(cxxRecordDecl(has(typedefNameDecl( - hasName("value_type"), - hasType(type( - hasUnqualifiedDesugaredType(recordType(hasDeclaration( - cxxRecordDecl(hasAnyName(SmallVector( - TupleTypes.begin(), TupleTypes.end()))))))))))))), - has(MakeTuple), hasSameNumArgsAsDeclNumParams(), - unless(isInTemplateInstantiation())) - .bind("emplacy_call")), + traverse(TK_AsIs, + cxxMemberCallExpr( + CallEmplacy, + on(hasType(cxxRecordDecl(has(typedefNameDecl( + hasName("value_type"), + hasType(hasCanonicalType(recordType(hasDeclaration( + cxxRecordDecl(hasAnyName(SmallVector( + TupleTypes.begin(), TupleTypes.end())))))))))))), + has(MakeTuple), hasSameNumArgsAsDeclNumParams(), + unless(isInTemplateInstantiation())) + .bind("emplacy_call")), this); } diff --git a/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp index 52e9a9f8d49e0..5310f2fd25381 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp @@ -29,7 +29,7 @@ static bool isLockGuardDecl(const NamedDecl *Decl) { static bool isLockGuard(const QualType &Type) { if (const auto *Record = Type->getAs()) - if (const RecordDecl *Decl = Record->getDecl()) + if (const RecordDecl *Decl = Record->getOriginalDecl()) return isLockGuardDecl(Decl); if (const auto *TemplateSpecType = Type->getAs()) @@ -89,17 +89,6 @@ findLocksInCompoundStmt(const CompoundStmt *Block, return LockGuardGroups; } -static TemplateSpecializationTypeLoc -getTemplateLockGuardTypeLoc(const TypeSourceInfo *SourceInfo) { - const TypeLoc Loc = SourceInfo->getTypeLoc(); - - const auto ElaboratedLoc = Loc.getAs(); - if (!ElaboratedLoc) - return {}; - - return ElaboratedLoc.getNamedTypeLoc().getAs(); -} - // Find the exact source range of the 'lock_guard' token static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo) { const TypeLoc LockGuardTypeLoc = SourceInfo->getTypeLoc(); @@ -110,7 +99,7 @@ static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo) { // Find the exact source range of the 'lock_guard' name token static SourceRange getLockGuardNameRange(const TypeSourceInfo *SourceInfo) { const TemplateSpecializationTypeLoc TemplateLoc = - getTemplateLockGuardTypeLoc(SourceInfo); + SourceInfo->getTypeLoc().getAs(); if (!TemplateLoc) return {}; @@ -136,11 +125,11 @@ void UseScopedLockCheck::registerMatchers(MatchFinder *Finder) { const auto LockGuardClassDecl = namedDecl(hasName("lock_guard"), isInStdNamespace()); - const auto LockGuardType = qualType(anyOf( - hasUnqualifiedDesugaredType( - recordType(hasDeclaration(LockGuardClassDecl))), - elaboratedType(namesType(hasUnqualifiedDesugaredType( - templateSpecializationType(hasDeclaration(LockGuardClassDecl))))))); + const auto LockGuardType = + qualType(anyOf(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(LockGuardClassDecl))), + hasUnqualifiedDesugaredType(templateSpecializationType( + hasDeclaration(LockGuardClassDecl))))); const auto LockVarDecl = varDecl(hasType(LockGuardType)); @@ -165,18 +154,16 @@ void UseScopedLockCheck::registerMatchers(MatchFinder *Finder) { if (WarnOnUsingAndTypedef) { // Match 'typedef std::lock_guard Lock' Finder->addMatcher(typedefDecl(unless(isExpansionInSystemHeader()), - hasUnderlyingType(LockGuardType)) + hasType(hasUnderlyingType(LockGuardType))) .bind("lock-guard-typedef"), this); // Match 'using Lock = std::lock_guard' - Finder->addMatcher( - typeAliasDecl( - unless(isExpansionInSystemHeader()), - hasType(elaboratedType(namesType(templateSpecializationType( - hasDeclaration(LockGuardClassDecl)))))) - .bind("lock-guard-using-alias"), - this); + Finder->addMatcher(typeAliasDecl(unless(isExpansionInSystemHeader()), + hasType(templateSpecializationType( + hasDeclaration(LockGuardClassDecl)))) + .bind("lock-guard-using-alias"), + this); // Match 'using std::lock_guard' Finder->addMatcher( @@ -288,8 +275,8 @@ void UseScopedLockCheck::diagOnSourceInfo( const ast_matchers::MatchFinder::MatchResult &Result) { const TypeLoc TL = LockGuardSourceInfo->getTypeLoc(); - if (const auto ElaboratedTL = TL.getAs()) { - auto Diag = diag(ElaboratedTL.getBeginLoc(), UseScopedLockMessage); + if (const auto TTL = TL.getAs()) { + auto Diag = diag(TTL.getBeginLoc(), UseScopedLockMessage); const SourceRange LockGuardRange = getLockGuardNameRange(LockGuardSourceInfo); diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp index ced4825f79a99..82f64096cbec1 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp @@ -64,66 +64,65 @@ struct UnqualNameVisitor : public RecursiveASTVisitor { return false; } - bool TraverseTypeLoc(TypeLoc TL, bool Elaborated = false) { + bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) { if (TL.isNull()) return true; - if (!Elaborated) { - switch (TL.getTypeLocClass()) { - case TypeLoc::Record: - if (visitUnqualName( - TL.getAs().getTypePtr()->getDecl()->getName())) - return false; + switch (TL.getTypeLocClass()) { + case TypeLoc::InjectedClassName: + case TypeLoc::Record: + case TypeLoc::Enum: { + auto TTL = TL.getAs(); + const auto *T = TTL.getTypePtr(); + if (T->getKeyword() != ElaboratedTypeKeyword::None || + TTL.getQualifierLoc()) break; - case TypeLoc::Enum: - if (visitUnqualName( - TL.getAs().getTypePtr()->getDecl()->getName())) - return false; - break; - case TypeLoc::TemplateSpecialization: - if (visitUnqualName(TL.getAs() - .getTypePtr() - ->getTemplateName() - .getAsTemplateDecl() - ->getName())) - return false; - break; - case TypeLoc::Typedef: - if (visitUnqualName( - TL.getAs().getTypePtr()->getDecl()->getName())) - return false; + if (visitUnqualName(T->getOriginalDecl()->getName())) + return false; + break; + } + case TypeLoc::TemplateSpecialization: { + auto TTL = TL.getAs(); + const auto *T = TTL.getTypePtr(); + if (T->getKeyword() != ElaboratedTypeKeyword::None || + TTL.getQualifierLoc()) break; - case TypeLoc::Using: - if (visitUnqualName(TL.getAs() - .getTypePtr() - ->getFoundDecl() - ->getName())) - return false; + if (visitUnqualName(T->getTemplateName().getAsTemplateDecl()->getName())) + return false; + break; + } + case TypeLoc::Typedef: { + auto TTL = TL.getAs(); + const auto *T = TTL.getTypePtr(); + if (T->getKeyword() != ElaboratedTypeKeyword::None || + TTL.getQualifierLoc()) break; - default: + if (visitUnqualName(T->getDecl()->getName())) + return false; + break; + } + case TypeLoc::Using: { + auto TTL = TL.getAs(); + const auto *T = TTL.getTypePtr(); + if (T->getKeyword() != ElaboratedTypeKeyword::None || + TTL.getQualifierLoc()) break; - } + if (visitUnqualName(T->getDecl()->getName())) + return false; + break; + } + default: + break; } - return RecursiveASTVisitor::TraverseTypeLoc(TL); + return RecursiveASTVisitor::TraverseTypeLoc( + TL, TraverseQualifier); } // Replace the base method in order to call our own // TraverseTypeLoc(). - bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL) { - return TraverseTypeLoc(TL.getUnqualifiedLoc()); - } - - // Replace the base version to inform TraverseTypeLoc that the type is - // elaborated. - bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc TL) { - if (TL.getQualifierLoc() && - !TraverseNestedNameSpecifierLoc(TL.getQualifierLoc())) - return false; - const auto *T = TL.getTypePtr(); - return TraverseTypeLoc(TL.getNamedTypeLoc(), - T->getKeyword() != ElaboratedTypeKeyword::None || - T->getQualifier()); + bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL, bool TraverseQualifier) { + return TraverseTypeLoc(TL.getUnqualifiedLoc(), TraverseQualifier); } bool VisitDeclRefExpr(DeclRefExpr *S) { diff --git a/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp index a053c07f95ce2..2373a26fe48b4 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp @@ -37,15 +37,13 @@ void UseTransparentFunctorsCheck::registerMatchers(MatchFinder *Finder) { // Non-transparent functor mentioned as a template parameter. FIXIT. Finder->addMatcher( - loc(qualType( - unless(elaboratedType()), - hasDeclaration(classTemplateSpecializationDecl( - unless(hasAnyTemplateArgument(templateArgument(refersToType( - qualType(pointsTo(qualType(isAnyCharacter()))))))), - hasAnyTemplateArgument( - templateArgument(refersToType(qualType(hasDeclaration( - TransparentFunctors)))) - .bind("Functor")))))) + loc(qualType(hasDeclaration(classTemplateSpecializationDecl( + unless(hasAnyTemplateArgument(templateArgument(refersToType( + qualType(pointsTo(qualType(isAnyCharacter()))))))), + hasAnyTemplateArgument( + templateArgument(refersToType(qualType( + hasDeclaration(TransparentFunctors)))) + .bind("Functor")))))) .bind("FunctorParentLoc"), this); diff --git a/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp b/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp index bd9bdd1701975..8e4ed41c5f501 100644 --- a/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp +++ b/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp @@ -29,12 +29,13 @@ using namespace clang::ast_matchers; namespace clang::tidy::objc { -namespace { static constexpr StringRef WeakText = "__weak"; static constexpr StringRef StrongText = "__strong"; static constexpr StringRef UnsafeUnretainedText = "__unsafe_unretained"; +namespace { + /// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference /// Objective-C object (or block) variables or fields whose object lifetimes /// are not __unsafe_unretained. @@ -49,6 +50,8 @@ AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime, QT.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone; } +} // namespace + static std::optional fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range, StringRef Ownership) { @@ -93,8 +96,6 @@ fixItHintForVarDecl(const VarDecl *VD, const SourceManager &SM, return FixItHint::CreateInsertion(Range.getBegin(), "__unsafe_unretained "); } -} // namespace - void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( traverse( diff --git a/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp b/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp index ffbdb025848d7..01ee4d518b97c 100644 --- a/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp +++ b/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp @@ -27,11 +27,14 @@ enum NamingStyle { CategoryProperty = 2, }; +} // namespace + /// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to /// 'camelCase' or 'abc_camelCase'. For other cases the users need to /// come up with a proper name by their own. /// FIXME: provide fix for snake_case to snakeCase -FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) { +static FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, + NamingStyle Style) { auto Name = Decl->getName(); auto NewName = Decl->getName().str(); size_t Index = 0; @@ -50,7 +53,7 @@ FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) { return {}; } -std::string validPropertyNameRegex(bool UsedInMatcher) { +static std::string validPropertyNameRegex(bool UsedInMatcher) { // Allow any of these names: // foo // fooBar @@ -72,13 +75,13 @@ std::string validPropertyNameRegex(bool UsedInMatcher) { return StartMatcher + "([a-z]|[A-Z][A-Z0-9])[a-z0-9A-Z]*$"; } -bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) { +static bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) { auto RegexExp = llvm::Regex("^[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z0-9][a-zA-Z0-9_]+$"); return RegexExp.match(PropertyName); } -bool prefixedPropertyNameValid(llvm::StringRef PropertyName) { +static bool prefixedPropertyNameValid(llvm::StringRef PropertyName) { size_t Start = PropertyName.find_first_of('_'); assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size()); auto Prefix = PropertyName.substr(0, Start); @@ -88,7 +91,6 @@ bool prefixedPropertyNameValid(llvm::StringRef PropertyName) { auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(false))); return RegexExp.match(PropertyName.substr(Start + 1)); } -} // namespace void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(objcPropertyDecl( diff --git a/clang-tools-extra/clang-tidy/performance/NoAutomaticMoveCheck.cpp b/clang-tools-extra/clang-tidy/performance/NoAutomaticMoveCheck.cpp index 7022e9d784fa7..1c018999432e3 100644 --- a/clang-tools-extra/clang-tidy/performance/NoAutomaticMoveCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/NoAutomaticMoveCheck.cpp @@ -42,11 +42,11 @@ void NoAutomaticMoveCheck::registerMatchers(MatchFinder *Finder) { // A matcher for a `DstT::DstT(const Src&)` where DstT also has a // `DstT::DstT(Src&&)`. const auto LValueRefCtor = cxxConstructorDecl( - hasParameter(0, - hasType(lValueReferenceType(pointee(type().bind("SrcT"))))), + hasParameter(0, hasType(hasCanonicalType( + lValueReferenceType(pointee(type().bind("SrcT")))))), ofClass(cxxRecordDecl(hasMethod(cxxConstructorDecl( - hasParameter(0, hasType(rValueReferenceType( - pointee(type(equalsBoundNode("SrcT"))))))))))); + hasParameter(0, hasType(hasCanonicalType(rValueReferenceType( + pointee(type(equalsBoundNode("SrcT")))))))))))); // A matcher for `DstT::DstT(const Src&&)`, which typically comes from an // instantiation of `template DstT::DstT(U&&)`. diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp index 120f7fb749493..c413090b3a0a4 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -17,7 +17,6 @@ #include namespace clang::tidy::performance { -namespace { using namespace ::clang::ast_matchers; using llvm::StringRef; @@ -30,8 +29,8 @@ static constexpr StringRef MethodDeclId = "methodDecl"; static constexpr StringRef FunctionDeclId = "functionDecl"; static constexpr StringRef OldVarDeclId = "oldVarDecl"; -void recordFixes(const VarDecl &Var, ASTContext &Context, - DiagnosticBuilder &Diagnostic) { +static void recordFixes(const VarDecl &Var, ASTContext &Context, + DiagnosticBuilder &Diagnostic) { Diagnostic << utils::fixit::changeVarDeclToReference(Var, Context); if (!Var.getType().isLocalConstQualified()) { if (std::optional Fix = utils::fixit::addQualifierToVarDecl( @@ -40,8 +39,8 @@ void recordFixes(const VarDecl &Var, ASTContext &Context, } } -std::optional firstLocAfterNewLine(SourceLocation Loc, - SourceManager &SM) { +static std::optional firstLocAfterNewLine(SourceLocation Loc, + SourceManager &SM) { bool Invalid = false; const char *TextAfter = SM.getCharacterData(Loc, &Invalid); if (Invalid) { @@ -51,8 +50,8 @@ std::optional firstLocAfterNewLine(SourceLocation Loc, return Loc.getLocWithOffset(TextAfter[Offset] == '\0' ? Offset : Offset + 1); } -void recordRemoval(const DeclStmt &Stmt, ASTContext &Context, - DiagnosticBuilder &Diagnostic) { +static void recordRemoval(const DeclStmt &Stmt, ASTContext &Context, + DiagnosticBuilder &Diagnostic) { auto &SM = Context.getSourceManager(); // Attempt to remove trailing comments as well. auto Tok = utils::lexer::findNextTokenSkippingComments(Stmt.getEndLoc(), SM, @@ -74,6 +73,8 @@ void recordRemoval(const DeclStmt &Stmt, ASTContext &Context, } } +namespace { + AST_MATCHER_FUNCTION_P(StatementMatcher, isRefReturningMethodCallWithConstOverloads, std::vector, ExcludedContainerTypes) { @@ -130,6 +131,8 @@ AST_MATCHER_FUNCTION_P(StatementMatcher, initializerReturnsReferenceToConst, hasUnaryOperand(OldVarDeclRef))))); } +} // namespace + // This checks that the variable itself is only used as const, and also makes // sure that it does not reference another variable that could be modified in // the BlockStmt. It does this by checking the following: @@ -180,13 +183,13 @@ static bool isInitializingVariableImmutable( return false; } -bool isVariableUnused(const VarDecl &Var, const Stmt &BlockStmt, - ASTContext &Context) { +static bool isVariableUnused(const VarDecl &Var, const Stmt &BlockStmt, + ASTContext &Context) { return allDeclRefExprs(Var, BlockStmt, Context).empty(); } -const SubstTemplateTypeParmType *getSubstitutedType(const QualType &Type, - ASTContext &Context) { +static const SubstTemplateTypeParmType * +getSubstitutedType(const QualType &Type, ASTContext &Context) { auto Matches = match( qualType(anyOf(substTemplateTypeParmType().bind("subst"), hasDescendant(substTemplateTypeParmType().bind("subst")))), @@ -194,9 +197,9 @@ const SubstTemplateTypeParmType *getSubstitutedType(const QualType &Type, return selectFirst("subst", Matches); } -bool differentReplacedTemplateParams(const QualType &VarType, - const QualType &InitializerType, - ASTContext &Context) { +static bool differentReplacedTemplateParams(const QualType &VarType, + const QualType &InitializerType, + ASTContext &Context) { if (const SubstTemplateTypeParmType *VarTmplType = getSubstitutedType(VarType, Context)) { if (const SubstTemplateTypeParmType *InitializerTmplType = @@ -212,8 +215,8 @@ bool differentReplacedTemplateParams(const QualType &VarType, return false; } -QualType constructorArgumentType(const VarDecl *OldVar, - const BoundNodes &Nodes) { +static QualType constructorArgumentType(const VarDecl *OldVar, + const BoundNodes &Nodes) { if (OldVar) { return OldVar->getType(); } @@ -224,8 +227,6 @@ QualType constructorArgumentType(const VarDecl *OldVar, return MethodDecl->getReturnType(); } -} // namespace - UnnecessaryCopyInitialization::UnnecessaryCopyInitialization( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp index fbd2ba67805d9..c1aa52bacf99f 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -21,16 +21,14 @@ using namespace clang::ast_matchers; namespace clang::tidy::performance { -namespace { - -std::string paramNameOrIndex(StringRef Name, size_t Index) { +static std::string paramNameOrIndex(StringRef Name, size_t Index) { return (Name.empty() ? llvm::Twine('#') + llvm::Twine(Index + 1) : llvm::Twine('\'') + Name + llvm::Twine('\'')) .str(); } -bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl, - ASTContext &Context) { +static bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context) { auto Matches = match( traverse(TK_AsIs, decl(forEachDescendant(declRefExpr( @@ -41,8 +39,6 @@ bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl, return Matches.empty(); } -} // namespace - UnnecessaryValueParamCheck::UnnecessaryValueParamCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.cpp b/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.cpp index 3b4d65be7dfa1..5a3c9a4203eb9 100644 --- a/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.cpp +++ b/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.cpp @@ -15,10 +15,11 @@ namespace clang::tidy::portability { void StdAllocatorConstCheck::registerMatchers(MatchFinder *Finder) { // Match std::allocator. - auto AllocatorConst = + auto AllocatorConst = qualType(hasCanonicalType( recordType(hasDeclaration(classTemplateSpecializationDecl( hasName("::std::allocator"), - hasTemplateArgument(0, refersToType(qualType(isConstQualified())))))); + hasTemplateArgument(0, + refersToType(qualType(isConstQualified())))))))); auto HasContainerName = hasAnyName("::std::vector", "::std::deque", "::std::list", @@ -31,8 +32,10 @@ void StdAllocatorConstCheck::registerMatchers(MatchFinder *Finder) { // aren't caught. Finder->addMatcher( typeLoc( - templateSpecializationTypeLoc(), - loc(hasUnqualifiedDesugaredType(anyOf( + anyOf(templateSpecializationTypeLoc(), + qualifiedTypeLoc( + hasUnqualifiedLoc(templateSpecializationTypeLoc()))), + loc(qualType(anyOf( recordType(hasDeclaration(classTemplateSpecializationDecl( HasContainerName, anyOf( diff --git a/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.h b/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.h index d22d1aafa00af..87702af91bdb6 100644 --- a/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.h +++ b/clang-tools-extra/clang-tidy/portability/StdAllocatorConstCheck.h @@ -23,6 +23,9 @@ class StdAllocatorConstCheck : public ClangTidyCheck { public: StdAllocatorConstCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; diff --git a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp index ce736a8d16023..c4dc319f23c38 100644 --- a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -238,9 +238,12 @@ void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { ? MemberCallObject : (Pointee ? Pointee : Result.Nodes.getNodeAs("STLObject")); FixItHint Hint; - std::string ReplacementText = std::string( - Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), - *Result.SourceManager, getLangOpts())); + std::string ReplacementText = + E->isImplicitCXXThis() + ? "" + : std::string(Lexer::getSourceText( + CharSourceRange::getTokenRange(E->getSourceRange()), + *Result.SourceManager, getLangOpts())); const auto *OpCallExpr = dyn_cast(E); if (isBinaryOrTernary(E) || isa(E) || (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) { @@ -251,6 +254,8 @@ void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { // This can happen if the object is a smart pointer. Don't add anything // because a '->' is already there (PR#51776), just call the method. ReplacementText += "empty()"; + } else if (E->isImplicitCXXThis()) { + ReplacementText += "empty()"; } else if (E->getType()->isPointerType()) ReplacementText += "->empty()"; else diff --git a/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.h b/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.h index 79b0d39bed41b..1b12fec972998 100644 --- a/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.h +++ b/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.h @@ -23,6 +23,9 @@ class ConvertMemberFunctionsToStatic : public ClangTidyCheck { public: ConvertMemberFunctionsToStatic(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/readability/DeleteNullPointerCheck.h b/clang-tools-extra/clang-tidy/readability/DeleteNullPointerCheck.h index e9e7c942d1d2c..6e746d803d3ee 100644 --- a/clang-tools-extra/clang-tidy/readability/DeleteNullPointerCheck.h +++ b/clang-tools-extra/clang-tidy/readability/DeleteNullPointerCheck.h @@ -22,6 +22,9 @@ class DeleteNullPointerCheck : public ClangTidyCheck { public: DeleteNullPointerCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; std::optional getCheckTraversalKind() const override { diff --git a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp index b4a157c153bb9..9eef5c4db2d01 100644 --- a/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp @@ -122,15 +122,15 @@ AST_MATCHER(EnumDecl, hasSequentialInitialValues) { return !AllEnumeratorsArePowersOfTwo; } -std::string getName(const EnumDecl *Decl) { +} // namespace + +static std::string getName(const EnumDecl *Decl) { if (!Decl->getDeclName()) return ""; return Decl->getQualifiedNameAsString(); } -} // namespace - EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp index e1fb42b8210e2..2f59aaa86b157 100644 --- a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp @@ -144,6 +144,8 @@ struct CognitiveComplexity final { void account(SourceLocation Loc, unsigned short Nesting, Criteria C); }; +} // namespace + // All the possible messages that can be output. The choice of the message // to use is based of the combination of the CognitiveComplexity::Criteria. // It would be nice to have it in CognitiveComplexity struct, but then it is @@ -163,23 +165,27 @@ static const std::array Msgs = {{ }}; // Criteria is a bitset, thus a few helpers are needed. -CognitiveComplexity::Criteria operator|(CognitiveComplexity::Criteria LHS, - CognitiveComplexity::Criteria RHS) { +static CognitiveComplexity::Criteria +operator|(CognitiveComplexity::Criteria LHS, + CognitiveComplexity::Criteria RHS) { return static_cast(llvm::to_underlying(LHS) | llvm::to_underlying(RHS)); } -CognitiveComplexity::Criteria operator&(CognitiveComplexity::Criteria LHS, - CognitiveComplexity::Criteria RHS) { +static CognitiveComplexity::Criteria +operator&(CognitiveComplexity::Criteria LHS, + CognitiveComplexity::Criteria RHS) { return static_cast(llvm::to_underlying(LHS) & llvm::to_underlying(RHS)); } -CognitiveComplexity::Criteria &operator|=(CognitiveComplexity::Criteria &LHS, - CognitiveComplexity::Criteria RHS) { +static CognitiveComplexity::Criteria & +operator|=(CognitiveComplexity::Criteria &LHS, + CognitiveComplexity::Criteria RHS) { LHS = operator|(LHS, RHS); return LHS; } -CognitiveComplexity::Criteria &operator&=(CognitiveComplexity::Criteria &LHS, - CognitiveComplexity::Criteria RHS) { +static CognitiveComplexity::Criteria & +operator&=(CognitiveComplexity::Criteria &LHS, + CognitiveComplexity::Criteria RHS) { LHS = operator&(LHS, RHS); return LHS; } @@ -199,6 +205,8 @@ void CognitiveComplexity::account(SourceLocation Loc, unsigned short Nesting, Total += Increase; } +namespace { + class FunctionASTVisitor final : public RecursiveASTVisitor { using Base = RecursiveASTVisitor; diff --git a/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp b/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp index 5f19706e16866..6b10e6b206a31 100644 --- a/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp @@ -41,9 +41,11 @@ AST_MATCHER(Stmt, isNULLMacroExpansion) { return isNULLMacroExpansion(&Node, Finder->getASTContext()); } -StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind, - QualType Type, - ASTContext &Context) { +} // namespace + +static StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind, + QualType Type, + ASTContext &Context) { switch (CastExprKind) { case CK_IntegralToBoolean: return Type->isUnsignedIntegerType() ? "0u" : "0"; @@ -62,15 +64,15 @@ StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind, } } -bool isUnaryLogicalNotOperator(const Stmt *Statement) { +static bool isUnaryLogicalNotOperator(const Stmt *Statement) { const auto *UnaryOperatorExpr = dyn_cast(Statement); return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot; } -void fixGenericExprCastToBool(DiagnosticBuilder &Diag, - const ImplicitCastExpr *Cast, const Stmt *Parent, - ASTContext &Context, - bool UseUpperCaseLiteralSuffix) { +static void fixGenericExprCastToBool(DiagnosticBuilder &Diag, + const ImplicitCastExpr *Cast, + const Stmt *Parent, ASTContext &Context, + bool UseUpperCaseLiteralSuffix) { // In case of expressions like (! integer), we should remove the redundant not // operator and use inverted comparison (integer == 0). bool InvertComparison = @@ -133,8 +135,8 @@ void fixGenericExprCastToBool(DiagnosticBuilder &Diag, Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion); } -StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression, - ASTContext &Context) { +static StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression, + ASTContext &Context) { if (isNULLMacroExpansion(Expression, Context)) { return "false"; } @@ -161,7 +163,7 @@ StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression, return {}; } -bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) { +static bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) { SourceRange PrefixRange(Loc.getLocWithOffset(-1), Loc); StringRef SpaceBeforeStmtStr = Lexer::getSourceText( CharSourceRange::getCharRange(PrefixRange), Context.getSourceManager(), @@ -173,9 +175,10 @@ bool needsSpacePrefix(SourceLocation Loc, ASTContext &Context) { return !AllowedCharacters.contains(SpaceBeforeStmtStr.back()); } -void fixGenericExprCastFromBool(DiagnosticBuilder &Diag, - const ImplicitCastExpr *Cast, - ASTContext &Context, StringRef OtherType) { +static void fixGenericExprCastFromBool(DiagnosticBuilder &Diag, + const ImplicitCastExpr *Cast, + ASTContext &Context, + StringRef OtherType) { if (!Context.getLangOpts().CPlusPlus) { Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), (Twine("(") + OtherType + ")").str()); @@ -200,8 +203,9 @@ void fixGenericExprCastFromBool(DiagnosticBuilder &Diag, } } -StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral, - QualType DestType, ASTContext &Context) { +static StringRef +getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral, + QualType DestType, ASTContext &Context) { // Prior to C++11, false literal could be implicitly converted to pointer. if (!Context.getLangOpts().CPlusPlus11 && (DestType->isPointerType() || DestType->isMemberPointerType()) && @@ -222,8 +226,8 @@ StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral, return BoolLiteral->getValue() ? "1" : "0"; } -bool isCastAllowedInCondition(const ImplicitCastExpr *Cast, - ASTContext &Context) { +static bool isCastAllowedInCondition(const ImplicitCastExpr *Cast, + ASTContext &Context) { std::queue Q; Q.push(Cast); @@ -251,8 +255,6 @@ bool isCastAllowedInCondition(const ImplicitCastExpr *Cast, return false; } -} // anonymous namespace - ImplicitBoolConversionCheck::ImplicitBoolConversionCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp index 561f067d471d1..44a784bc9f21a 100644 --- a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp @@ -28,8 +28,11 @@ AST_MATCHER_P(QualType, hasUnqualifiedType, enum class Qualifier { Const, Volatile, Restrict }; -std::optional findQualToken(const VarDecl *Decl, Qualifier Qual, - const MatchFinder::MatchResult &Result) { +} // namespace + +static std::optional +findQualToken(const VarDecl *Decl, Qualifier Qual, + const MatchFinder::MatchResult &Result) { // Since either of the locs can be in a macro, use `makeFileCharRange` to be // sure that we have a consistent `CharSourceRange`, located entirely in the // source file. @@ -58,7 +61,7 @@ std::optional findQualToken(const VarDecl *Decl, Qualifier Qual, *Result.SourceManager); } -std::optional +static std::optional getTypeSpecifierLocation(const VarDecl *Var, const MatchFinder::MatchResult &Result) { SourceRange TypeSpecifier( @@ -73,8 +76,8 @@ getTypeSpecifierLocation(const VarDecl *Var, return TypeSpecifier; } -std::optional mergeReplacementRange(SourceRange &TypeSpecifier, - const Token &ConstToken) { +static std::optional +mergeReplacementRange(SourceRange &TypeSpecifier, const Token &ConstToken) { if (TypeSpecifier.getBegin().getLocWithOffset(-1) == ConstToken.getEndLoc()) { TypeSpecifier.setBegin(ConstToken.getLocation()); return std::nullopt; @@ -86,21 +89,19 @@ std::optional mergeReplacementRange(SourceRange &TypeSpecifier, return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc()); } -bool isPointerConst(QualType QType) { +static bool isPointerConst(QualType QType) { QualType Pointee = QType->getPointeeType(); assert(!Pointee.isNull() && "can't have a null Pointee"); return Pointee.isConstQualified(); } -bool isAutoPointerConst(QualType QType) { +static bool isAutoPointerConst(QualType QType) { QualType Pointee = cast(QType->getPointeeType().getTypePtr())->desugar(); assert(!Pointee.isNull() && "can't have a null Pointee"); return Pointee.isConstQualified(); } -} // namespace - QualifiedAutoCheck::QualifiedAutoCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp index 70016a372b8bd..d93077cc6884e 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp @@ -14,19 +14,18 @@ using namespace clang::ast_matchers; namespace clang::tidy::readability { -namespace { +static const char *const RedundantReturnDiag = + "redundant return statement at the end " + "of a function with a void return type"; +static const char *const RedundantContinueDiag = + "redundant continue statement at the " + "end of loop statement"; -const char *const RedundantReturnDiag = "redundant return statement at the end " - "of a function with a void return type"; -const char *const RedundantContinueDiag = "redundant continue statement at the " - "end of loop statement"; - -bool isLocationInMacroExpansion(const SourceManager &SM, SourceLocation Loc) { +static bool isLocationInMacroExpansion(const SourceManager &SM, + SourceLocation Loc) { return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); } -} // namespace - void RedundantControlFlowCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( functionDecl(isDefinition(), returns(voidType()), diff --git a/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp b/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp index fffb136e5a332..a7b3c4a1f7cf9 100644 --- a/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp @@ -19,19 +19,25 @@ namespace { AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } } // namespace -static unsigned getNameSpecifierNestingLevel(const QualType &QType) { - if (const auto *ElType = QType->getAs()) { - if (const NestedNameSpecifier *NestedSpecifiers = ElType->getQualifier()) { - unsigned NameSpecifierNestingLevel = 1; - do { - NameSpecifierNestingLevel++; - NestedSpecifiers = NestedSpecifiers->getPrefix(); - } while (NestedSpecifiers); - +static unsigned getNameSpecifierNestingLevel(QualType QType) { + unsigned NameSpecifierNestingLevel = 1; + for (NestedNameSpecifier Qualifier = QType->getPrefix(); /**/; + ++NameSpecifierNestingLevel) { + switch (Qualifier.getKind()) { + case NestedNameSpecifier::Kind::Null: return NameSpecifierNestingLevel; + case NestedNameSpecifier::Kind::Global: + case NestedNameSpecifier::Kind::MicrosoftSuper: + return NameSpecifierNestingLevel + 1; + case NestedNameSpecifier::Kind::Namespace: + Qualifier = Qualifier.getAsNamespaceAndPrefix().Prefix; + continue; + case NestedNameSpecifier::Kind::Type: + Qualifier = Qualifier.getAsType()->getPrefix(); + continue; } + llvm_unreachable("unhandled nested name specifier kind"); } - return 0; } void StaticAccessedThroughInstanceCheck::storeOptions( diff --git a/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h b/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h index 4044f13769aa5..9869855c17d6b 100644 --- a/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h +++ b/clang-tools-extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h @@ -24,6 +24,9 @@ class StaticAccessedThroughInstanceCheck : public ClangTidyCheck { : ClangTidyCheck(Name, Context), NameSpecifierNestingThreshold( Options.get("NameSpecifierNestingThreshold", 3U)) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; diff --git a/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp index 5a04029e4a6fa..447c2437666cf 100644 --- a/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp @@ -414,9 +414,9 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType, // Arithmetic types are interconvertible, except scoped enums. if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) { if ((ParamType->isEnumeralType() && - ParamType->castAs()->getDecl()->isScoped()) || + ParamType->castAs()->getOriginalDecl()->isScoped()) || (ArgType->isEnumeralType() && - ArgType->castAs()->getDecl()->isScoped())) + ArgType->castAs()->getOriginalDecl()->isScoped())) return false; return true; diff --git a/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h b/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h index 4dfcf36e4789f..2768955109d26 100644 --- a/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h +++ b/clang-tools-extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h @@ -21,6 +21,9 @@ namespace clang::tidy::readability { class UniqueptrDeleteReleaseCheck : public ClangTidyCheck { public: UniqueptrDeleteReleaseCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; void storeOptions(ClangTidyOptions::OptionMap &Opts) override; diff --git a/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp b/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp index 6f6b8a853a91e..718467ed02f0a 100644 --- a/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp @@ -67,11 +67,7 @@ static QualType getNonTemplateAlias(QualType QT) { if (!TT->getDecl()->getDescribedTemplate() && !TT->getDecl()->getDeclContext()->isDependentContext()) return QT; - QT = TT->getDecl()->getUnderlyingType(); - } - // cast to elaborated type - else if (const ElaboratedType *ET = dyn_cast(QT)) { - QT = ET->getNamedType(); + QT = TT->desugar(); } else { break; } diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp index 97dfd0fc943b4..bef3b938b5afd 100644 --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -162,14 +162,19 @@ in .clang-tidy file, if any. cl::init(false), cl::cat(ClangTidyCategory)); static cl::opt LineFilter("line-filter", desc(R"( -List of files with line ranges to filter the -warnings. Can be used together with --header-filter. The format of the list is a -JSON array of objects: +List of files and line ranges to output diagnostics from. +The range is inclusive on both ends. Can be used together +with -header-filter. The format of the list is a JSON +array of objects. For example: + [ {"name":"file1.cpp","lines":[[1,3],[5,7]]}, {"name":"file2.h"} ] + +This will output diagnostics from 'file1.cpp' only for +the line ranges [1,3] and [5,7], as well as all from the +entire 'file2.h'. )"), cl::init(""), cl::cat(ClangTidyCategory)); @@ -712,7 +717,7 @@ int clangTidyMain(int argc, const char **argv) { EnableModuleHeadersParsing); std::vector Errors = runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS, - FixNotes, EnableCheckProfile, ProfilePrefix); + FixNotes, EnableCheckProfile, ProfilePrefix, Quiet); bool FoundErrors = llvm::any_of(Errors, [](const ClangTidyError &E) { return E.DiagLevel == ClangTidyError::Error; }); diff --git a/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py b/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py index 7cd21afd70f7e..d7899e0a18d0c 100755 --- a/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py +++ b/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py @@ -28,6 +28,7 @@ import json import multiprocessing import os +import queue import re import shutil import subprocess @@ -42,13 +43,6 @@ except ImportError: yaml = None -is_py2 = sys.version[0] == "2" - -if is_py2: - import Queue as queue -else: - import queue as queue - def run_tidy(task_queue, lock, timeout, failed_files): watchdog = None diff --git a/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py b/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py index 80f1766b0bf10..670e0a2c7678a 100755 --- a/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py +++ b/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py @@ -483,7 +483,7 @@ async def main() -> None: parser.add_argument( "-line-filter", default=None, - help="List of files with line ranges to filter the warnings.", + help="List of files and line ranges to output diagnostics from.", ) if yaml: parser.add_argument( diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp index 0637d0eff688c..aa6aefcf0c493 100644 --- a/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp +++ b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp @@ -66,7 +66,8 @@ ExceptionSpecAnalyzer::analyzeBase(const CXXBaseSpecifier &Base, if (!RecType) return State::Unknown; - const auto *BaseClass = cast(RecType->getDecl()); + const auto *BaseClass = + cast(RecType->getOriginalDecl())->getDefinitionOrSelf(); return analyzeRecord(BaseClass, Kind); } diff --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp index e1c1bee97f6d4..0df8e913100fc 100644 --- a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp +++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp @@ -461,8 +461,9 @@ bool FormatStringConverter::emitIntegerArgument( // the signedness based on the format string, so we need to do the // same. if (const auto *ET = ArgType->getAs()) { - if (const std::optional MaybeCastType = - castTypeForArgument(ArgKind, ET->getDecl()->getIntegerType())) + if (const std::optional MaybeCastType = castTypeForArgument( + ArgKind, + ET->getOriginalDecl()->getDefinitionOrSelf()->getIntegerType())) ArgFixes.emplace_back( ArgIndex, (Twine("static_cast<") + *MaybeCastType + ">(").str()); else diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.cpp b/clang-tools-extra/clang-tidy/utils/Matchers.cpp index 4974a9cdb9f4b..bd7b03eb39ad7 100644 --- a/clang-tools-extra/clang-tidy/utils/Matchers.cpp +++ b/clang-tools-extra/clang-tidy/utils/Matchers.cpp @@ -34,7 +34,7 @@ bool MatchesAnyListedTypeNameMatcher::matches( PrintingPolicy PrintingPolicyWithSuppressedTag( Finder->getASTContext().getLangOpts()); PrintingPolicyWithSuppressedTag.PrintAsCanonical = CanonicalTypes; - PrintingPolicyWithSuppressedTag.SuppressElaboration = true; + PrintingPolicyWithSuppressedTag.FullyQualifiedName = true; PrintingPolicyWithSuppressedTag.SuppressScope = false; PrintingPolicyWithSuppressedTag.SuppressTagKeyword = true; PrintingPolicyWithSuppressedTag.SuppressUnwrittenScope = true; diff --git a/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp index eaa04fef08c43..90539eaabbe03 100644 --- a/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp +++ b/clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp @@ -194,6 +194,8 @@ class RenamerClangTidyCheckPPCallbacks : public PPCallbacks { return; if (SM.isWrittenInCommandLineFile(MacroNameTok.getLocation())) return; + if (SM.isInSystemHeader(MacroNameTok.getLocation())) + return; Check->checkMacro(MacroNameTok, Info, SM); } @@ -281,9 +283,10 @@ class RenamerClangTidyVisitor } bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Loc) { - if (const NestedNameSpecifier *Spec = Loc.getNestedNameSpecifier()) { + if (NestedNameSpecifier Spec = Loc.getNestedNameSpecifier(); + Spec.getKind() == NestedNameSpecifier::Kind::Namespace) { if (const auto *Decl = - dyn_cast_if_present(Spec->getAsNamespace())) + dyn_cast(Spec.getAsNamespaceAndPrefix().Namespace)) Check->addUsage(Decl, Loc.getLocalSourceRange(), SM); } @@ -323,48 +326,34 @@ class RenamerClangTidyVisitor } bool VisitTypedefTypeLoc(const TypedefTypeLoc &Loc) { - Check->addUsage(Loc.getTypedefNameDecl(), Loc.getSourceRange(), SM); + Check->addUsage(Loc.getDecl(), Loc.getNameLoc(), SM); return true; } bool VisitTagTypeLoc(const TagTypeLoc &Loc) { - Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); - return true; - } - - bool VisitInjectedClassNameTypeLoc(const InjectedClassNameTypeLoc &Loc) { - Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); + Check->addUsage(Loc.getOriginalDecl(), Loc.getNameLoc(), SM); return true; } bool VisitUnresolvedUsingTypeLoc(const UnresolvedUsingTypeLoc &Loc) { - Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); + Check->addUsage(Loc.getDecl(), Loc.getNameLoc(), SM); return true; } bool VisitTemplateTypeParmTypeLoc(const TemplateTypeParmTypeLoc &Loc) { - Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); + Check->addUsage(Loc.getDecl(), Loc.getNameLoc(), SM); return true; } bool VisitTemplateSpecializationTypeLoc(const TemplateSpecializationTypeLoc &Loc) { const TemplateDecl *Decl = - Loc.getTypePtr()->getTemplateName().getAsTemplateDecl(); + Loc.getTypePtr()->getTemplateName().getAsTemplateDecl( + /*IgnoreDeduced=*/true); - SourceRange Range(Loc.getTemplateNameLoc(), Loc.getTemplateNameLoc()); - if (const auto *ClassDecl = dyn_cast(Decl)) { + if (const auto *ClassDecl = dyn_cast(Decl)) if (const NamedDecl *TemplDecl = ClassDecl->getTemplatedDecl()) - Check->addUsage(TemplDecl, Range, SM); - } - - return true; - } - - bool VisitDependentTemplateSpecializationTypeLoc( - const DependentTemplateSpecializationTypeLoc &Loc) { - if (const TagDecl *Decl = Loc.getTypePtr()->getAsTagDecl()) - Check->addUsage(Decl, Loc.getSourceRange(), SM); + Check->addUsage(TemplDecl, Loc.getTemplateNameLoc(), SM); return true; } diff --git a/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp b/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp index 44db0c2aed607..5518afd32e7f0 100644 --- a/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp +++ b/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp @@ -13,16 +13,14 @@ namespace clang::tidy::utils::type_traits { -namespace { - -bool classHasTrivialCopyAndDestroy(QualType Type) { +static bool classHasTrivialCopyAndDestroy(QualType Type) { auto *Record = Type->getAsCXXRecordDecl(); return Record && Record->hasDefinition() && !Record->hasNonTrivialCopyConstructor() && !Record->hasNonTrivialDestructor(); } -bool hasDeletedCopyConstructor(QualType Type) { +static bool hasDeletedCopyConstructor(QualType Type) { auto *Record = Type->getAsCXXRecordDecl(); if (!Record || !Record->hasDefinition()) return false; @@ -33,8 +31,6 @@ bool hasDeletedCopyConstructor(QualType Type) { return false; } -} // namespace - std::optional isExpensiveToCopy(QualType Type, const ASTContext &Context) { if (Type->isDependentType() || Type->isIncompleteType()) @@ -124,7 +120,8 @@ bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context) { return true; if (const auto *RT = CanonicalType->getAs()) { - return recordIsTriviallyDefaultConstructible(*RT->getDecl(), Context); + return recordIsTriviallyDefaultConstructible( + *RT->getOriginalDecl()->getDefinitionOrSelf(), Context); } // No other types can match. diff --git a/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.h b/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.h index e1f1f06ab3735..b2d5ab61fb0dc 100644 --- a/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.h +++ b/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.h @@ -24,6 +24,9 @@ class TemporaryObjectsCheck : public ClangTidyCheck { TemporaryObjectsCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), Names(utils::options::parseStringList(Options.get("Names", ""))) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index f2631e5abb6a3..2f46ecc92576c 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -102,54 +102,78 @@ getUsingNamespaceDirectives(const DeclContext *DestContext, // ancestor is redundant, therefore we stop at lowest common ancestor. // In addition to that stops early whenever IsVisible returns true. This can be // used to implement support for "using namespace" decls. -std::string -getQualification(ASTContext &Context, const DeclContext *DestContext, - const DeclContext *SourceContext, - llvm::function_ref IsVisible) { - std::vector Parents; - bool ReachedNS = false; +std::string getQualification(ASTContext &Context, + const DeclContext *DestContext, + const DeclContext *SourceContext, + llvm::function_ref IsVisible) { + std::vector Parents; + [[maybe_unused]] bool ReachedNS = false; for (const DeclContext *CurContext = SourceContext; CurContext; CurContext = CurContext->getLookupParent()) { // Stop once we reach a common ancestor. if (CurContext->Encloses(DestContext)) break; - NestedNameSpecifier *NNS = nullptr; + const Decl *CurD; if (auto *TD = llvm::dyn_cast(CurContext)) { // There can't be any more tag parents after hitting a namespace. assert(!ReachedNS); - (void)ReachedNS; - NNS = NestedNameSpecifier::Create(Context, nullptr, TD->getTypeForDecl()); + CurD = TD; } else if (auto *NSD = llvm::dyn_cast(CurContext)) { ReachedNS = true; - NNS = NestedNameSpecifier::Create(Context, nullptr, NSD); // Anonymous and inline namespace names are not spelled while qualifying // a name, so skip those. if (NSD->isAnonymousNamespace() || NSD->isInlineNamespace()) continue; + CurD = NSD; } else { // Other types of contexts cannot be spelled in code, just skip over // them. continue; } // Stop if this namespace is already visible at DestContext. - if (IsVisible(NNS)) + if (IsVisible(CurD)) break; - Parents.push_back(NNS); + Parents.push_back(CurD); + } + + // Go over the declarations in reverse order, since we stored inner-most + // parent first. + NestedNameSpecifier Qualifier = std::nullopt; + bool IsFirst = true; + for (const auto *CurD : llvm::reverse(Parents)) { + if (auto *TD = llvm::dyn_cast(CurD)) { + QualType T; + if (const auto *RD = dyn_cast(TD); + ClassTemplateDecl *CTD = RD->getDescribedClassTemplate()) { + ArrayRef Args; + if (const auto *SD = dyn_cast(RD)) + Args = SD->getTemplateArgs().asArray(); + else + Args = CTD->getTemplateParameters()->getInjectedTemplateArgs(Context); + T = Context.getTemplateSpecializationType( + ElaboratedTypeKeyword::None, + Context.getQualifiedTemplateName( + Qualifier, /*TemplateKeyword=*/!IsFirst, TemplateName(CTD)), + Args, /*CanonicalArgs=*/{}, Context.getCanonicalTagType(RD)); + } else { + T = Context.getTagType(ElaboratedTypeKeyword::None, Qualifier, TD, + /*OwnsTag=*/false); + } + Qualifier = NestedNameSpecifier(T.getTypePtr()); + } else { + Qualifier = + NestedNameSpecifier(Context, cast(CurD), Qualifier); + } + IsFirst = false; } + if (!Qualifier) + return ""; - // Go over name-specifiers in reverse order to create necessary qualification, - // since we stored inner-most parent first. std::string Result; llvm::raw_string_ostream OS(Result); - for (const auto *Parent : llvm::reverse(Parents)) { - if (Parent != *Parents.rbegin() && Parent->isDependent() && - Parent->getAsRecordDecl() && - Parent->getAsRecordDecl()->getDescribedClassTemplate()) - OS << "template "; - Parent->print(OS, Context.getPrintingPolicy()); - } + Qualifier.print(OS, Context.getPrintingPolicy()); return OS.str(); } @@ -187,6 +211,7 @@ std::string printQualifiedName(const NamedDecl &ND) { // include them, but at query time it's hard to find all the inline // namespaces to query: the preamble doesn't have a dedicated list. Policy.SuppressUnwrittenScope = true; + Policy.SuppressScope = true; // (unnamed struct), not (unnamed struct at /path/to/foo.cc:42:1). // In clangd, context is usually available and paths are mostly noise. Policy.AnonymousTagLocations = false; @@ -213,8 +238,7 @@ std::string printUsingNamespaceName(const ASTContext &Ctx, std::string Name; llvm::raw_string_ostream Out(Name); - if (auto *Qual = D.getQualifier()) - Qual->print(Out, PP); + D.getQualifier().print(Out, PP); D.getNominatedNamespaceAsWritten()->printName(Out); return Out.str(); } @@ -229,8 +253,7 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { // Handle 'using namespace'. They all have the same name - . if (auto *UD = llvm::dyn_cast(&ND)) { Out << "using namespace "; - if (auto *Qual = UD->getQualifier()) - Qual->print(Out, PP); + UD->getQualifier().print(Out, PP); UD->getNominatedNamespaceAsWritten()->printName(Out); return Out.str(); } @@ -250,8 +273,7 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND) { } // Print nested name qualifier if it was written in the source code. - if (auto *Qualifier = getQualifierLoc(ND).getNestedNameSpecifier()) - Qualifier->print(Out, PP); + getQualifierLoc(ND).getNestedNameSpecifier().print(Out, PP); // Print the name itself. ND.getDeclName().print(Out, PP); // Print template arguments. @@ -391,12 +413,13 @@ preferredIncludeDirective(llvm::StringRef FileName, const LangOptions &LangOpts, } std::string printType(const QualType QT, const DeclContext &CurContext, - const llvm::StringRef Placeholder) { + const llvm::StringRef Placeholder, bool FullyQualify) { std::string Result; llvm::raw_string_ostream OS(Result); PrintingPolicy PP(CurContext.getParentASTContext().getPrintingPolicy()); PP.SuppressTagKeyword = true; PP.SuppressUnwrittenScope = true; + PP.FullyQualifiedName = FullyQualify; class PrintCB : public PrintingCallbacks { public: @@ -439,6 +462,7 @@ QualType declaredType(const TypeDecl *D) { if (const auto *CTSD = llvm::dyn_cast(D)) if (const auto *Args = CTSD->getTemplateArgsAsWritten()) return Context.getTemplateSpecializationType( + ElaboratedTypeKeyword::None, TemplateName(CTSD->getSpecializedTemplate()), Args->arguments(), /*CanonicalArgs=*/{}); return Context.getTypeDeclType(D); @@ -664,13 +688,10 @@ std::string getQualification(ASTContext &Context, auto VisibleNamespaceDecls = getUsingNamespaceDirectives(DestContext, InsertionPoint); return getQualification( - Context, DestContext, ND->getDeclContext(), - [&](NestedNameSpecifier *NNS) { - const NamespaceDecl *NS = - dyn_cast_if_present(NNS->getAsNamespace()); - if (!NS) + Context, DestContext, ND->getDeclContext(), [&](const Decl *D) { + if (D->getKind() != Decl::Namespace) return false; - NS = NS->getCanonicalDecl(); + const auto *NS = cast(D)->getCanonicalDecl(); return llvm::any_of(VisibleNamespaceDecls, [NS](const NamespaceDecl *NSD) { return NSD->getCanonicalDecl() == NS; @@ -687,12 +708,11 @@ std::string getQualification(ASTContext &Context, (void)NS; } return getQualification( - Context, DestContext, ND->getDeclContext(), - [&](NestedNameSpecifier *NNS) { + Context, DestContext, ND->getDeclContext(), [&](const Decl *D) { return llvm::any_of(VisibleNamespaces, [&](llvm::StringRef Namespace) { std::string NS; llvm::raw_string_ostream OS(NS); - NNS->print(OS, Context.getPrintingPolicy()); + D->print(OS, Context.getPrintingPolicy()); return OS.str() == Namespace; }); }); @@ -965,7 +985,7 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) { // Recurse on pack parameters size_t Depth = 0; const FunctionDecl *CurrentFunction = D; - llvm::SmallSet SeenTemplates; + llvm::SmallPtrSet SeenTemplates; if (const auto *Template = D->getPrimaryTemplate()) { SeenTemplates.insert(Template); } diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index fb0722d697cd0..1538d12172593 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -135,7 +135,8 @@ preferredIncludeDirective(llvm::StringRef FileName, const LangOptions &LangOpts, /// Returns a QualType as string. The result doesn't contain unwritten scopes /// like anonymous/inline namespace. std::string printType(const QualType QT, const DeclContext &CurContext, - llvm::StringRef Placeholder = ""); + llvm::StringRef Placeholder = "", + bool FullyQualify = false); /// Indicates if \p D is a template instantiation implicitly generated by the /// compiler, e.g. diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index a1e9da41b4b32..fb3f05329be21 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -6,7 +6,7 @@ add_subdirectory(support) # Configure the Features.inc file. if (NOT DEFINED CLANGD_BUILD_XPC) - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") set(CLANGD_BUILD_XPC_DEFAULT ON) else () set(CLANGD_BUILD_XPC_DEFAULT OFF) @@ -108,6 +108,7 @@ add_clang_library(clangDaemon STATIC SemanticHighlighting.cpp SemanticSelection.cpp SourceCode.cpp + SymbolDocumentation.cpp SystemIncludeExtractor.cpp TidyProvider.cpp TUScheduler.cpp @@ -192,7 +193,7 @@ if(CLANGD_TIDY_CHECKS) endif() add_subdirectory(refactor/tweaks) -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") # FIXME: Make fuzzer not use linux-specific APIs, build it everywhere. add_subdirectory(fuzzer) endif() diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 9c17b4ca9b706..16559e4a35155 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -1466,19 +1466,15 @@ bool allowIndex(CodeCompletionContext &CC) { auto Scope = CC.getCXXScopeSpecifier(); if (!Scope) return true; - NestedNameSpecifier *NameSpec = (*Scope)->getScopeRep(); - if (!NameSpec) - return true; // We only query the index when qualifier is a namespace. // If it's a class, we rely solely on sema completions. - switch (NameSpec->getKind()) { - case NestedNameSpecifier::Global: - case NestedNameSpecifier::Namespace: + switch ((*Scope)->getScopeRep().getKind()) { + case NestedNameSpecifier::Kind::Null: + case NestedNameSpecifier::Kind::Global: + case NestedNameSpecifier::Kind::Namespace: return true; - case NestedNameSpecifier::Super: - case NestedNameSpecifier::TypeSpec: - // Unresolved inside a template. - case NestedNameSpecifier::Identifier: + case NestedNameSpecifier::Kind::MicrosoftSuper: + case NestedNameSpecifier::Kind::Type: return false; } llvm_unreachable("invalid NestedNameSpecifier kind"); @@ -2434,6 +2430,9 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const { } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CodeCompletion &C) { + OS << "Signature: " << "\"" << C.Signature << "\", " + << "SnippetSuffix: " << "\"" << C.SnippetSuffix << "\"" + << ", Rendered:"; // For now just lean on CompletionItem. return OS << C.render(CodeCompleteOptions()); } diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index 9b4442b0bb76f..d6579640cb0fb 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -7,13 +7,18 @@ //===----------------------------------------------------------------------===// #include "CodeCompletionStrings.h" +#include "Config.h" +#include "SymbolDocumentation.h" #include "clang-c/Index.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Comment.h" +#include "clang/AST/Decl.h" #include "clang/AST/RawCommentList.h" #include "clang/Basic/SourceManager.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" #include #include @@ -100,16 +105,51 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { // the comments for namespaces. return ""; } - const RawComment *RC = getCompletionComment(Ctx, &Decl); - if (!RC) - return ""; - // Sanity check that the comment does not come from the PCH. We choose to not - // write them into PCH, because they are racy and slow to load. - assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); - std::string Doc = - RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); - if (!looksLikeDocComment(Doc)) - return ""; + + const RawComment *RC = nullptr; + const Config &Cfg = Config::current(); + + std::string Doc; + + if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen && + isa(Decl)) { + // Parameters are documented in their declaration context (function or + // template function). + const NamedDecl *ND = dyn_cast(Decl.getDeclContext()); + if (!ND) + return ""; + + RC = getCompletionComment(Ctx, ND); + if (!RC) + return ""; + + // Sanity check that the comment does not come from the PCH. We choose to + // not write them into PCH, because they are racy and slow to load. + assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); + + comments::FullComment *FC = RC->parse(Ctx, /*PP=*/nullptr, ND); + if (!FC) + return ""; + + SymbolDocCommentVisitor V(FC, Ctx.getLangOpts().CommentOpts); + std::string RawDoc; + llvm::raw_string_ostream OS(RawDoc); + + V.parameterDocToString(dyn_cast(&Decl)->getName(), OS); + + Doc = StringRef(RawDoc).trim().str(); + } else { + RC = getCompletionComment(Ctx, &Decl); + if (!RC) + return ""; + // Sanity check that the comment does not come from the PCH. We choose to + // not write them into PCH, because they are racy and slow to load. + assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); + Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); + if (!looksLikeDocComment(Doc)) + return ""; + } + // Clang requires source to be UTF-8, but doesn't enforce this in comments. if (!llvm::json::isUTF8(Doc)) Doc = llvm::json::fixUTF8(Doc); diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h index a6a7cd53fb9bf..0f11f37e14698 100644 --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -315,7 +315,7 @@ struct Fragment { /// AngledHeaders (i.e. a header matches a regex in both QuotedHeaders and /// AngledHeaders), system headers use <> and non-system headers use "". /// These can match any suffix of the header file in question. - /// Matching is performed against the header text, not its absolute path + /// Matching is performed against the absolute path of the header /// within the project. std::vector> QuotedHeaders; /// List of regexes for headers that should always be included with a @@ -323,7 +323,7 @@ struct Fragment { /// AngledHeaders (i.e. a header matches a regex in both QuotedHeaders and /// AngledHeaders), system headers use <> and non-system headers use "". /// These can match any suffix of the header file in question. - /// Matching is performed against the header text, not its absolute path + /// Matching is performed against the absolute path of the header /// within the project. std::vector> AngledHeaders; }; diff --git a/clang-tools-extra/clangd/DumpAST.cpp b/clang-tools-extra/clangd/DumpAST.cpp index c6075e75e9a6b..9a8d41d870929 100644 --- a/clang-tools-extra/clangd/DumpAST.cpp +++ b/clang-tools-extra/clangd/DumpAST.cpp @@ -147,17 +147,17 @@ class DumpVisitor : public RecursiveASTVisitor { } llvm_unreachable("Unhandled ArgKind enum"); } - std::string getKind(const NestedNameSpecifierLoc &NNSL) { - assert(NNSL.getNestedNameSpecifier()); - switch (NNSL.getNestedNameSpecifier()->getKind()) { + std::string getKind(NestedNameSpecifierLoc NNSL) { + switch (NNSL.getNestedNameSpecifier().getKind()) { + case NestedNameSpecifier::Kind::Null: + llvm_unreachable("unexpected null nested name specifier"); #define NNS_KIND(X) \ - case NestedNameSpecifier::X: \ + case NestedNameSpecifier::Kind::X: \ return #X - NNS_KIND(Identifier); NNS_KIND(Namespace); - NNS_KIND(TypeSpec); + NNS_KIND(Type); NNS_KIND(Global); - NNS_KIND(Super); + NNS_KIND(MicrosoftSuper); #undef NNS_KIND } llvm_unreachable("Unhandled SpecifierKind enum"); @@ -261,7 +261,7 @@ class DumpVisitor : public RecursiveASTVisitor { return TL.getType().getLocalQualifiers().getAsString( Ctx.getPrintingPolicy()); if (const auto *TT = dyn_cast(TL.getTypePtr())) - return getDetail(TT->getDecl()); + return getDetail(TT->getOriginalDecl()); if (const auto *DT = dyn_cast(TL.getTypePtr())) if (DT->isDeduced()) return DT->getDeducedType().getAsString(Ctx.getPrintingPolicy()); @@ -273,16 +273,11 @@ class DumpVisitor : public RecursiveASTVisitor { return getDetail(TT->getDecl()); return ""; } - std::string getDetail(const NestedNameSpecifierLoc &NNSL) { - const auto &NNS = *NNSL.getNestedNameSpecifier(); - switch (NNS.getKind()) { - case NestedNameSpecifier::Identifier: - return NNS.getAsIdentifier()->getName().str() + "::"; - case NestedNameSpecifier::Namespace: - return NNS.getAsNamespace()->getNameAsString() + "::"; - default: + std::string getDetail(NestedNameSpecifierLoc NNSL) { + NestedNameSpecifier NNS = NNSL.getNestedNameSpecifier(); + if (NNS.getKind() != NestedNameSpecifier::Kind::Namespace) return ""; - } + return NNS.getAsNamespaceAndPrefix().Namespace->getNameAsString() + "::"; } std::string getDetail(const CXXCtorInitializer *CCI) { if (FieldDecl *FD = CCI->getAnyMember()) @@ -346,8 +341,10 @@ class DumpVisitor : public RecursiveASTVisitor { return !D || isInjectedClassName(D) || traverseNode("declaration", D, [&] { Base::TraverseDecl(D); }); } - bool TraverseTypeLoc(TypeLoc TL) { - return !TL || traverseNode("type", TL, [&] { Base::TraverseTypeLoc(TL); }); + bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) { + return !TL || traverseNode("type", TL, [&] { + Base::TraverseTypeLoc(TL, TraverseQualifier); + }); } bool TraverseTemplateName(const TemplateName &TN) { return traverseNode("template name", TN, @@ -389,11 +386,11 @@ class DumpVisitor : public RecursiveASTVisitor { // This means we'd never see 'int' in 'const int'! Work around that here. // (The reason for the behavior is to avoid traversing the nested Type twice, // but we ignore TraverseType anyway). - bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QTL) { + bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QTL, bool TraverseQualifier) { return TraverseTypeLoc(QTL.getUnqualifiedLoc()); } // Uninteresting parts of the AST that don't have locations within them. - bool TraverseNestedNameSpecifier(NestedNameSpecifier *) { return true; } + bool TraverseNestedNameSpecifier(NestedNameSpecifier) { return true; } bool TraverseType(QualType) { return true; } // OpaqueValueExpr blocks traversal, we must explicitly traverse it. @@ -420,7 +417,7 @@ ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens, V.TraverseNestedNameSpecifierLoc( *const_cast(NNSL)); else if (const auto *NNS = N.get()) - V.TraverseNestedNameSpecifier(const_cast(NNS)); + V.TraverseNestedNameSpecifier(*NNS); else if (const auto *TL = N.get()) V.TraverseTypeLoc(*const_cast(TL)); else if (const auto *QT = N.get()) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index b1089577ba819..32018d1bf3a84 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -366,19 +366,11 @@ struct TargetFinder { Visitor(TargetFinder &Outer, RelSet Flags) : Outer(Outer), Flags(Flags) {} void VisitTagType(const TagType *TT) { - Outer.add(TT->getAsTagDecl(), Flags); - } - - void VisitElaboratedType(const ElaboratedType *ET) { - Outer.add(ET->desugar(), Flags); + Outer.add(cast(TT)->getOriginalDecl(), Flags); } void VisitUsingType(const UsingType *ET) { - Outer.add(ET->getFoundDecl(), Flags); - } - - void VisitInjectedClassNameType(const InjectedClassNameType *ICNT) { - Outer.add(ICNT->getDecl(), Flags); + Outer.add(ET->getDecl(), Flags); } void VisitDecltypeType(const DecltypeType *DTT) { @@ -483,30 +475,27 @@ struct TargetFinder { Visitor(*this, Flags).Visit(T.getTypePtr()); } - void add(const NestedNameSpecifier *NNS, RelSet Flags) { + void add(NestedNameSpecifier NNS, RelSet Flags) { if (!NNS) return; - debug(*NNS, Flags); - switch (NNS->getKind()) { - case NestedNameSpecifier::Namespace: - add(NNS->getAsNamespace(), Flags); - return; - case NestedNameSpecifier::Identifier: - if (Resolver) { - add(Resolver->resolveNestedNameSpecifierToType(NNS), Flags); - } + debug(NNS, Flags); + switch (NNS.getKind()) { + case NestedNameSpecifier::Kind::Namespace: + add(NNS.getAsNamespaceAndPrefix().Namespace, Flags); return; - case NestedNameSpecifier::TypeSpec: - add(QualType(NNS->getAsType(), 0), Flags); + case NestedNameSpecifier::Kind::Type: + add(QualType(NNS.getAsType(), 0), Flags); return; - case NestedNameSpecifier::Global: + case NestedNameSpecifier::Kind::Global: // This should be TUDecl, but we can't get a pointer to it! return; - case NestedNameSpecifier::Super: - add(NNS->getAsRecordDecl(), Flags); + case NestedNameSpecifier::Kind::MicrosoftSuper: + add(NNS.getAsMicrosoftSuper(), Flags); return; + case NestedNameSpecifier::Kind::Null: + llvm_unreachable("unexpected null nested name specifier"); } - llvm_unreachable("unhandled NestedNameSpecifier::SpecifierKind"); + llvm_unreachable("unhandled NestedNameSpecifier::Kind"); } void add(const CXXCtorInitializer *CCI, RelSet Flags) { @@ -555,7 +544,7 @@ allTargetDecls(const DynTypedNode &N, const HeuristicResolver *Resolver) { else if (const NestedNameSpecifierLoc *NNSL = N.get()) Finder.add(NNSL->getNestedNameSpecifier(), Flags); else if (const NestedNameSpecifier *NNS = N.get()) - Finder.add(NNS, Flags); + Finder.add(*NNS, Flags); else if (const TypeLoc *TL = N.get()) Finder.add(TL->getType(), Flags); else if (const QualType *QT = N.get()) @@ -861,32 +850,25 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { const HeuristicResolver *Resolver; llvm::SmallVector Refs; - void VisitElaboratedTypeLoc(ElaboratedTypeLoc L) { - // We only know about qualifier, rest if filled by inner locations. - size_t InitialSize = Refs.size(); - Visit(L.getNamedTypeLoc().getUnqualifiedLoc()); - size_t NewSize = Refs.size(); - // Add qualifier for the newly-added refs. - for (unsigned I = InitialSize; I < NewSize; ++I) { - ReferenceLoc *Ref = &Refs[I]; - // Fill in the qualifier. - assert(!Ref->Qualifier.hasQualifier() && "qualifier already set"); - Ref->Qualifier = L.getQualifierLoc(); - } + void VisitUnresolvedUsingTypeLoc(UnresolvedUsingTypeLoc L) { + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), + L.getLocalSourceRange().getBegin(), + /*IsDecl=*/false, + {L.getDecl()}}); } void VisitUsingTypeLoc(UsingTypeLoc L) { - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), L.getLocalSourceRange().getBegin(), /*IsDecl=*/false, - {L.getFoundDecl()}}); + {L.getDecl()}}); } void VisitTagTypeLoc(TagTypeLoc L) { - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, - {L.getDecl()}}); + {L.getOriginalDecl()}}); } void VisitTemplateTypeParmTypeLoc(TemplateTypeParmTypeLoc L) { @@ -906,25 +888,18 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { // 2. 'vector' with mask 'Underlying'. // we want to return only #1 in this case. Refs.push_back(ReferenceLoc{ - NestedNameSpecifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false, + L.getQualifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false, explicitReferenceTargets(DynTypedNode::create(L.getType()), DeclRelation::Alias, Resolver)}); } void VisitDeducedTemplateSpecializationTypeLoc( DeducedTemplateSpecializationTypeLoc L) { Refs.push_back(ReferenceLoc{ - NestedNameSpecifierLoc(), L.getNameLoc(), /*IsDecl=*/false, + L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, explicitReferenceTargets(DynTypedNode::create(L.getType()), DeclRelation::Alias, Resolver)}); } - void VisitInjectedClassNameTypeLoc(InjectedClassNameTypeLoc TL) { - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), - TL.getNameLoc(), - /*IsDecl=*/false, - {TL.getDecl()}}); - } - void VisitDependentTemplateSpecializationTypeLoc( DependentTemplateSpecializationTypeLoc L) { Refs.push_back( @@ -943,12 +918,12 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { } void VisitTypedefTypeLoc(TypedefTypeLoc L) { - if (shouldSkipTypedef(L.getTypedefNameDecl())) + if (shouldSkipTypedef(L.getDecl())) return; - Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + Refs.push_back(ReferenceLoc{L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false, - {L.getTypedefNameDecl()}}); + {L.getDecl()}}); } void VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc L) { @@ -980,17 +955,6 @@ class ExplicitReferenceCollector return true; } - bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc L) { - // ElaboratedTypeLoc will reports information for its inner type loc. - // Otherwise we loose information about inner types loc's qualifier. - TypeLoc Inner = L.getNamedTypeLoc().getUnqualifiedLoc(); - if (L.getBeginLoc() == Inner.getBeginLoc()) - return RecursiveASTVisitor::TraverseTypeLoc(Inner); - else - TypeLocsToSkip.insert(Inner.getBeginLoc()); - return RecursiveASTVisitor::TraverseElaboratedTypeLoc(L); - } - bool VisitStmt(Stmt *S) { visitNode(DynTypedNode::create(*S)); return true; @@ -1051,7 +1015,7 @@ class ExplicitReferenceCollector return true; visitNode(DynTypedNode::create(L)); // Inner type is missing information about its qualifier, skip it. - if (auto TL = L.getTypeLoc()) + if (auto TL = L.getAsTypeLoc()) TypeLocsToSkip.insert(TL.getBeginLoc()); return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(L); } @@ -1092,12 +1056,21 @@ class ExplicitReferenceCollector if (auto *S = N.get()) return refInStmt(S, Resolver); if (auto *NNSL = N.get()) { - // (!) 'DeclRelation::Alias' ensures we do not loose namespace aliases. - return {ReferenceLoc{ - NNSL->getPrefix(), NNSL->getLocalBeginLoc(), false, - explicitReferenceTargets( - DynTypedNode::create(*NNSL->getNestedNameSpecifier()), - DeclRelation::Alias, Resolver)}}; + // (!) 'DeclRelation::Alias' ensures we do not lose namespace aliases. + NestedNameSpecifierLoc Qualifier; + SourceLocation NameLoc; + if (auto TL = NNSL->getAsTypeLoc()) { + Qualifier = TL.getPrefix(); + NameLoc = TL.getNonPrefixBeginLoc(); + } else { + Qualifier = NNSL->getAsNamespaceAndPrefix().Prefix; + NameLoc = NNSL->getLocalBeginLoc(); + } + return { + ReferenceLoc{Qualifier, NameLoc, false, + explicitReferenceTargets( + DynTypedNode::create(NNSL->getNestedNameSpecifier()), + DeclRelation::Alias, Resolver)}}; } if (const TypeLoc *TL = N.get()) return refInTypeLoc(*TL, Resolver); @@ -1210,8 +1183,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ReferenceLoc R) { OS << "}"; if (R.Qualifier) { OS << ", qualifier = '"; - R.Qualifier.getNestedNameSpecifier()->print(OS, - PrintingPolicy(LangOptions())); + R.Qualifier.getNestedNameSpecifier().print(OS, + PrintingPolicy(LangOptions())); OS << "'"; } if (R.IsDecl) diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp index 7c0eb9651feaa..c6afd0bc07cbd 100644 --- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp +++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @@ -833,6 +833,10 @@ bool OverlayCDB::setCompileCommand(PathRef File, std::unique_ptr OverlayCDB::getProjectModules(PathRef File) const { auto MDB = DelegatingCDB::getProjectModules(File); + if (!MDB) { + log("Failed to get compilation Database for {0}", File); + return {}; + } MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command, PathRef CommandPath) { Mangler(Command, CommandPath); diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp index 87fd261b906e6..b9d67cc6a1602 100644 --- a/clang-tools-extra/clangd/Headers.cpp +++ b/clang-tools-extra/clangd/Headers.cpp @@ -304,16 +304,17 @@ IncludeInserter::calculateIncludePath(const HeaderFile &InsertedHeader, // FIXME: should we allow (some limited number of) "../header.h"? if (llvm::sys::path::is_absolute(Suggested)) return std::nullopt; + auto HeaderPath = llvm::sys::path::convert_to_slash(InsertedHeader.File); bool IsAngled = false; for (auto &Filter : AngledHeaders) { - if (Filter(Suggested)) { + if (Filter(HeaderPath)) { IsAngled = true; break; } } bool IsQuoted = false; for (auto &Filter : QuotedHeaders) { - if (Filter(Suggested)) { + if (Filter(HeaderPath)) { IsQuoted = true; break; } @@ -324,7 +325,7 @@ IncludeInserter::calculateIncludePath(const HeaderFile &InsertedHeader, if (IsAngled && IsQuoted) { elog("Header '{0}' matches both quoted and angled regexes, default will " "be used.", - Suggested); + HeaderPath); } IsAngled = IsAngledByDefault; } diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 1e0718d673260..a7cf45c632827 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -18,6 +18,7 @@ #include "Protocol.h" #include "Selection.h" #include "SourceCode.h" +#include "SymbolDocumentation.h" #include "clang-include-cleaner/Analysis.h" #include "clang-include-cleaner/IncludeSpeller.h" #include "clang-include-cleaner/Types.h" @@ -41,6 +42,7 @@ #include "clang/AST/Type.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" @@ -170,13 +172,14 @@ HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx, QT = QT->castAs()->getUnderlyingType(); HoverInfo::PrintedType Result; llvm::raw_string_ostream OS(Result.Type); - // Special case: if the outer type is a tag type without qualifiers, then - // include the tag for extra clarity. - // This isn't very idiomatic, so don't attempt it for complex cases, including - // pointers/references, template specializations, etc. + // Special case: if the outer type is a canonical tag type, then include the + // tag for extra clarity. This isn't very idiomatic, so don't attempt it for + // complex cases, including pointers/references, template specializations, + // etc. if (!QT.isNull() && !QT.hasQualifiers() && PP.SuppressTagKeyword) { - if (auto *TT = llvm::dyn_cast(QT.getTypePtr())) - OS << TT->getDecl()->getKindName() << " "; + if (auto *TT = llvm::dyn_cast(QT.getTypePtr()); + TT && TT->isCanonicalUnqualified()) + OS << TT->getOriginalDecl()->getKindName() << " "; } QT.print(OS, PP); @@ -452,7 +455,7 @@ std::optional printExprValue(const Expr *E, // Compare to int64_t to avoid bit-width match requirements. int64_t Val = Constant.Val.getInt().getExtValue(); for (const EnumConstantDecl *ECD : - T->castAs()->getDecl()->enumerators()) + T->castAs()->getOriginalDecl()->enumerators()) if (ECD->getInitVal() == Val) return llvm::formatv("{0} ({1})", ECD->getNameAsString(), printHex(Constant.Val.getInt())) @@ -627,6 +630,9 @@ HoverInfo getHoverContents(const NamedDecl *D, const PrintingPolicy &PP, HI.Name = printName(Ctx, *D); const auto *CommentD = getDeclForComment(D); HI.Documentation = getDeclComment(Ctx, *CommentD); + // save the language options to be able to create the comment::CommandTraits + // to parse the documentation + HI.CommentOpts = D->getASTContext().getLangOpts().CommentOpts; enhanceFromIndex(HI, *CommentD, Index); if (HI.Documentation.empty()) HI.Documentation = synthesizeDocumentation(D); @@ -967,10 +973,11 @@ void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) { const auto &Ctx = ND.getASTContext(); if (auto *RD = llvm::dyn_cast(&ND)) { - if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RD->getTypeForDecl())) + CanQualType RT = Ctx.getCanonicalTagType(RD); + if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RT)) HI.Size = Size->getQuantity() * 8; if (!RD->isDependentType() && RD->isCompleteDefinition()) - HI.Align = Ctx.getTypeAlign(RD->getTypeForDecl()); + HI.Align = Ctx.getTypeAlign(RT); return; } @@ -1388,9 +1395,100 @@ static std::string formatOffset(uint64_t OffsetInBits) { return Offset; } -markup::Document HoverInfo::present() const { - markup::Document Output; +void HoverInfo::calleeArgInfoToMarkupParagraph(markup::Paragraph &P) const { + assert(CallPassType); + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + OS << "Passed "; + if (CallPassType->PassBy != HoverInfo::PassType::Value) { + OS << "by "; + if (CallPassType->PassBy == HoverInfo::PassType::ConstRef) + OS << "const "; + OS << "reference "; + } + if (CalleeArgInfo->Name) + OS << "as " << CalleeArgInfo->Name; + else if (CallPassType->PassBy == HoverInfo::PassType::Value) + OS << "by value"; + if (CallPassType->Converted && CalleeArgInfo->Type) + OS << " (converted to " << CalleeArgInfo->Type->Type << ")"; + P.appendText(OS.str()); +} + +void HoverInfo::usedSymbolNamesToMarkup(markup::Document &Output) const { + markup::Paragraph &P = Output.addParagraph(); + P.appendText("provides "); + + const std::vector::size_type SymbolNamesLimit = 5; + auto Front = llvm::ArrayRef(UsedSymbolNames).take_front(SymbolNamesLimit); + + llvm::interleave( + Front, [&](llvm::StringRef Sym) { P.appendCode(Sym); }, + [&] { P.appendText(", "); }); + if (UsedSymbolNames.size() > Front.size()) { + P.appendText(" and "); + P.appendText(std::to_string(UsedSymbolNames.size() - Front.size())); + P.appendText(" more"); + } +} + +void HoverInfo::providerToMarkupParagraph(markup::Document &Output) const { + markup::Paragraph &DI = Output.addParagraph(); + DI.appendText("provided by"); + DI.appendSpace(); + DI.appendCode(Provider); +} + +void HoverInfo::definitionScopeToMarkup(markup::Document &Output) const { + std::string Buffer; + + // Append scope comment, dropping trailing "::". + // Note that we don't print anything for global namespace, to not annoy + // non-c++ projects or projects that are not making use of namespaces. + if (!LocalScope.empty()) { + // Container name, e.g. class, method, function. + // We might want to propagate some info about container type to print + // function foo, class X, method X::bar, etc. + Buffer += "// In " + llvm::StringRef(LocalScope).rtrim(':').str() + '\n'; + } else if (NamespaceScope && !NamespaceScope->empty()) { + Buffer += "// In namespace " + + llvm::StringRef(*NamespaceScope).rtrim(':').str() + '\n'; + } + + if (!AccessSpecifier.empty()) { + Buffer += AccessSpecifier + ": "; + } + Buffer += Definition; + + Output.addCodeBlock(Buffer, DefinitionLanguage); +} + +void HoverInfo::valueToMarkupParagraph(markup::Paragraph &P) const { + P.appendText("Value = "); + P.appendCode(*Value); +} + +void HoverInfo::offsetToMarkupParagraph(markup::Paragraph &P) const { + P.appendText("Offset: " + formatOffset(*Offset)); +} + +void HoverInfo::sizeToMarkupParagraph(markup::Paragraph &P) const { + P.appendText("Size: " + formatSize(*Size)); + if (Padding && *Padding != 0) { + P.appendText(llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str()); + } + if (Align) + P.appendText(", alignment " + formatSize(*Align)); +} + +markup::Document HoverInfo::presentDoxygen() const { + // NOTE: this function is currently almost identical to presentDefault(). + // This is to have a minimal change when introducing the doxygen parser. + // This function will be changed when rearranging the output for doxygen + // parsed documentation. + + markup::Document Output; // Header contains a text of the form: // variable `var` // @@ -1407,14 +1505,99 @@ markup::Document HoverInfo::present() const { if (Kind != index::SymbolKind::Unknown) Header.appendText(index::getSymbolKindString(Kind)).appendSpace(); assert(!Name.empty() && "hover triggered on a nameless symbol"); + Header.appendCode(Name); if (!Provider.empty()) { - markup::Paragraph &DI = Output.addParagraph(); - DI.appendText("provided by"); - DI.appendSpace(); - DI.appendCode(Provider); + providerToMarkupParagraph(Output); + } + + // Put a linebreak after header to increase readability. + Output.addRuler(); + // Print Types on their own lines to reduce chances of getting line-wrapped by + // editor, as they might be long. + if (ReturnType) { + // For functions we display signature in a list form, e.g.: + // → `x` + // Parameters: + // - `bool param1` + // - `int param2 = 5` + Output.addParagraph().appendText("→ ").appendCode( + llvm::to_string(*ReturnType)); + } + + SymbolDocCommentVisitor SymbolDoc(Documentation, CommentOpts); + + if (Parameters && !Parameters->empty()) { + Output.addParagraph().appendText("Parameters:"); + markup::BulletList &L = Output.addBulletList(); + for (const auto &Param : *Parameters) { + markup::Paragraph &P = L.addItem().addParagraph(); + P.appendCode(llvm::to_string(Param)); + + if (SymbolDoc.isParameterDocumented(llvm::to_string(Param.Name))) { + P.appendText(" -"); + SymbolDoc.parameterDocToMarkup(llvm::to_string(Param.Name), P); + } + } + } + // Don't print Type after Parameters or ReturnType as this will just duplicate + // the information + if (Type && !ReturnType && !Parameters) + Output.addParagraph().appendText("Type: ").appendCode( + llvm::to_string(*Type)); + + if (Value) { + valueToMarkupParagraph(Output.addParagraph()); + } + + if (Offset) + offsetToMarkupParagraph(Output.addParagraph()); + if (Size) { + sizeToMarkupParagraph(Output.addParagraph()); + } + + if (CalleeArgInfo) { + calleeArgInfoToMarkupParagraph(Output.addParagraph()); + } + + SymbolDoc.docToMarkup(Output); + + if (!Definition.empty()) { Output.addRuler(); + definitionScopeToMarkup(Output); + } + + if (!UsedSymbolNames.empty()) { + Output.addRuler(); + usedSymbolNamesToMarkup(Output); + } + + return Output; +} + +markup::Document HoverInfo::presentDefault() const { + markup::Document Output; + // Header contains a text of the form: + // variable `var` + // + // class `X` + // + // function `foo` + // + // expression + // + // Note that we are making use of a level-3 heading because VSCode renders + // level 1 and 2 headers in a huge font, see + // https://github.com/microsoft/vscode/issues/88417 for details. + markup::Paragraph &Header = Output.addHeading(3); + if (Kind != index::SymbolKind::Unknown) + Header.appendText(index::getSymbolKindString(Kind)).appendSpace(); + assert(!Name.empty() && "hover triggered on a nameless symbol"); + Header.appendCode(Name); + + if (!Provider.empty()) { + providerToMarkupParagraph(Output); } // Put a linebreak after header to increase readability. @@ -1445,41 +1628,17 @@ markup::Document HoverInfo::present() const { llvm::to_string(*Type)); if (Value) { - markup::Paragraph &P = Output.addParagraph(); - P.appendText("Value = "); - P.appendCode(*Value); + valueToMarkupParagraph(Output.addParagraph()); } if (Offset) - Output.addParagraph().appendText("Offset: " + formatOffset(*Offset)); + offsetToMarkupParagraph(Output.addParagraph()); if (Size) { - auto &P = Output.addParagraph().appendText("Size: " + formatSize(*Size)); - if (Padding && *Padding != 0) { - P.appendText( - llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str()); - } - if (Align) - P.appendText(", alignment " + formatSize(*Align)); + sizeToMarkupParagraph(Output.addParagraph()); } if (CalleeArgInfo) { - assert(CallPassType); - std::string Buffer; - llvm::raw_string_ostream OS(Buffer); - OS << "Passed "; - if (CallPassType->PassBy != HoverInfo::PassType::Value) { - OS << "by "; - if (CallPassType->PassBy == HoverInfo::PassType::ConstRef) - OS << "const "; - OS << "reference "; - } - if (CalleeArgInfo->Name) - OS << "as " << CalleeArgInfo->Name; - else if (CallPassType->PassBy == HoverInfo::PassType::Value) - OS << "by value"; - if (CallPassType->Converted && CalleeArgInfo->Type) - OS << " (converted to " << CalleeArgInfo->Type->Type << ")"; - Output.addParagraph().appendText(OS.str()); + calleeArgInfoToMarkupParagraph(Output.addParagraph()); } if (!Documentation.empty()) @@ -1487,49 +1646,12 @@ markup::Document HoverInfo::present() const { if (!Definition.empty()) { Output.addRuler(); - std::string Buffer; - - if (!Definition.empty()) { - // Append scope comment, dropping trailing "::". - // Note that we don't print anything for global namespace, to not annoy - // non-c++ projects or projects that are not making use of namespaces. - if (!LocalScope.empty()) { - // Container name, e.g. class, method, function. - // We might want to propagate some info about container type to print - // function foo, class X, method X::bar, etc. - Buffer += - "// In " + llvm::StringRef(LocalScope).rtrim(':').str() + '\n'; - } else if (NamespaceScope && !NamespaceScope->empty()) { - Buffer += "// In namespace " + - llvm::StringRef(*NamespaceScope).rtrim(':').str() + '\n'; - } - - if (!AccessSpecifier.empty()) { - Buffer += AccessSpecifier + ": "; - } - - Buffer += Definition; - } - - Output.addCodeBlock(Buffer, DefinitionLanguage); + definitionScopeToMarkup(Output); } if (!UsedSymbolNames.empty()) { Output.addRuler(); - markup::Paragraph &P = Output.addParagraph(); - P.appendText("provides "); - - const std::vector::size_type SymbolNamesLimit = 5; - auto Front = llvm::ArrayRef(UsedSymbolNames).take_front(SymbolNamesLimit); - - llvm::interleave( - Front, [&](llvm::StringRef Sym) { P.appendCode(Sym); }, - [&] { P.appendText(", "); }); - if (UsedSymbolNames.size() > Front.size()) { - P.appendText(" and "); - P.appendText(std::to_string(UsedSymbolNames.size() - Front.size())); - P.appendText(" more"); - } + usedSymbolNamesToMarkup(Output); } return Output; @@ -1538,21 +1660,19 @@ markup::Document HoverInfo::present() const { std::string HoverInfo::present(MarkupKind Kind) const { if (Kind == MarkupKind::Markdown) { const Config &Cfg = Config::current(); - if ((Cfg.Documentation.CommentFormat == - Config::CommentFormatPolicy::Markdown) || - (Cfg.Documentation.CommentFormat == - Config::CommentFormatPolicy::Doxygen)) - // If the user prefers Markdown, we use the present() method to generate - // the Markdown output. - return present().asMarkdown(); + if (Cfg.Documentation.CommentFormat == + Config::CommentFormatPolicy::Markdown) + return presentDefault().asMarkdown(); + if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen) + return presentDoxygen().asMarkdown(); if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::PlainText) // If the user prefers plain text, we use the present() method to generate // the plain text output. - return present().asEscapedMarkdown(); + return presentDefault().asEscapedMarkdown(); } - return present().asPlainText(); + return presentDefault().asPlainText(); } // If the backtick at `Offset` starts a probable quoted range, return the range diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h index 2f65431bd72de..614180a7b9846 100644 --- a/clang-tools-extra/clangd/Hover.h +++ b/clang-tools-extra/clangd/Hover.h @@ -74,6 +74,8 @@ struct HoverInfo { std::optional SymRange; index::SymbolKind Kind = index::SymbolKind::Unknown; std::string Documentation; + // required to create a comments::CommandTraits object without the ASTContext + CommentOptions CommentOpts; /// Source code containing the definition of the symbol. std::string Definition; const char *DefinitionLanguage = "cpp"; @@ -118,10 +120,23 @@ struct HoverInfo { // alphabetical order. std::vector UsedSymbolNames; - /// Produce a user-readable information. - markup::Document present() const; - + /// Produce a user-readable information based on the specified markup kind. std::string present(MarkupKind Kind) const; + +private: + void usedSymbolNamesToMarkup(markup::Document &Output) const; + void providerToMarkupParagraph(markup::Document &Output) const; + void definitionScopeToMarkup(markup::Document &Output) const; + void calleeArgInfoToMarkupParagraph(markup::Paragraph &P) const; + void valueToMarkupParagraph(markup::Paragraph &P) const; + void offsetToMarkupParagraph(markup::Paragraph &P) const; + void sizeToMarkupParagraph(markup::Paragraph &P) const; + + /// Parse and render the hover information as Doxygen documentation. + markup::Document presentDoxygen() const; + + /// Render the hover information as a default documentation. + markup::Document presentDefault() const; }; inline bool operator==(const HoverInfo::PrintedType &LHS, diff --git a/clang-tools-extra/clangd/IncludeFixer.cpp b/clang-tools-extra/clangd/IncludeFixer.cpp index 50bc2bd7ccb94..c27d960cd963b 100644 --- a/clang-tools-extra/clangd/IncludeFixer.cpp +++ b/clang-tools-extra/clangd/IncludeFixer.cpp @@ -173,7 +173,7 @@ std::vector IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel, // `enum x : int;' is not formally an incomplete type. // We may need a full definition anyway. if (auto * ET = llvm::dyn_cast(T)) - if (!ET->getDecl()->getDefinition()) + if (!ET->getOriginalDecl()->getDefinition()) return fixIncompleteType(*T); } } @@ -400,35 +400,35 @@ std::optional extractUnresolvedNameCheaply( CheapUnresolvedName Result; Result.Name = Unresolved.getAsString(); if (SS && SS->isNotEmpty()) { // "::" or "ns::" - if (auto *Nested = SS->getScopeRep()) { - if (Nested->getKind() == NestedNameSpecifier::Global) { - Result.ResolvedScope = ""; - } else if (const NamespaceBaseDecl *NSB = Nested->getAsNamespace()) { - if (const auto *NS = dyn_cast(NSB)) { - std::string SpecifiedNS = printNamespaceScope(*NS); - std::optional Spelling = getSpelledSpecifier(*SS, SM); - - // Check the specifier spelled in the source. - // If the resolved scope doesn't end with the spelled scope, the - // resolved scope may come from a sema typo correction. For example, - // sema assumes that "clangd::" is a typo of "clang::" and uses - // "clang::" as the specified scope in: - // namespace clang { clangd::X; } - // In this case, we use the "typo" specifier as extra scope instead - // of using the scope assumed by sema. - if (!Spelling || llvm::StringRef(SpecifiedNS).ends_with(*Spelling)) { - Result.ResolvedScope = std::move(SpecifiedNS); - } else { - Result.UnresolvedScope = std::move(*Spelling); - } + NestedNameSpecifier Nested = SS->getScopeRep(); + if (Nested.getKind() == NestedNameSpecifier::Kind::Global) { + Result.ResolvedScope = ""; + } else if (Nested.getKind() == NestedNameSpecifier::Kind::Namespace) { + const NamespaceBaseDecl *NSB = Nested.getAsNamespaceAndPrefix().Namespace; + if (const auto *NS = dyn_cast(NSB)) { + std::string SpecifiedNS = printNamespaceScope(*NS); + std::optional Spelling = getSpelledSpecifier(*SS, SM); + + // Check the specifier spelled in the source. + // If the resolved scope doesn't end with the spelled scope, the + // resolved scope may come from a sema typo correction. For example, + // sema assumes that "clangd::" is a typo of "clang::" and uses + // "clang::" as the specified scope in: + // namespace clang { clangd::X; } + // In this case, we use the "typo" specifier as extra scope instead + // of using the scope assumed by sema. + if (!Spelling || llvm::StringRef(SpecifiedNS).ends_with(*Spelling)) { + Result.ResolvedScope = std::move(SpecifiedNS); } else { - Result.ResolvedScope = printNamespaceScope(*cast(NSB)->getNamespace()); + Result.UnresolvedScope = std::move(*Spelling); } } else { - // We don't fix symbols in scopes that are not top-level e.g. class - // members, as we don't collect includes for them. - return std::nullopt; + Result.ResolvedScope = printNamespaceScope(*cast(NSB)->getNamespace()); } + } else { + // We don't fix symbols in scopes that are not top-level e.g. class + // members, as we don't collect includes for them. + return std::nullopt; } } diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp index 197c62c40dcf0..cd479e1b7c9bc 100644 --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -55,18 +55,24 @@ void stripLeadingUnderscores(StringRef &Name) { Name = Name.ltrim('_'); } // getDeclForType() returns the decl responsible for Type's spelling. // This is the inverse of ASTContext::getTypeDeclType(). -template getDecl())> -const NamedDecl *getDeclForTypeImpl(const Ty *T) { - return T->getDecl(); -} -const NamedDecl *getDeclForTypeImpl(const void *T) { return nullptr; } const NamedDecl *getDeclForType(const Type *T) { switch (T->getTypeClass()) { -#define ABSTRACT_TYPE(TY, BASE) -#define TYPE(TY, BASE) \ - case Type::TY: \ - return getDeclForTypeImpl(llvm::cast(T)); -#include "clang/AST/TypeNodes.inc" + case Type::Enum: + case Type::Record: + case Type::InjectedClassName: + return cast(T)->getOriginalDecl(); + case Type::TemplateSpecialization: + return cast(T) + ->getTemplateName() + .getAsTemplateDecl(/*IgnoreDeduced=*/true); + case Type::Typedef: + return cast(T)->getDecl(); + case Type::UnresolvedUsing: + return cast(T)->getDecl(); + case Type::Using: + return cast(T)->getDecl(); + default: + return nullptr; } llvm_unreachable("Unknown TypeClass enum"); } @@ -81,8 +87,6 @@ llvm::StringRef getSimpleName(const NamedDecl &D) { return getSimpleName(D.getDeclName()); } llvm::StringRef getSimpleName(QualType T) { - if (const auto *ET = llvm::dyn_cast(T)) - return getSimpleName(ET->getNamedType()); if (const auto *BT = llvm::dyn_cast(T)) { PrintingPolicy PP(LangOptions{}); PP.adjustForCPlusPlus(); diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp index 277cb8769a1b1..06165dfbbcdd2 100644 --- a/clang-tools-extra/clangd/Selection.cpp +++ b/clang-tools-extra/clangd/Selection.cpp @@ -62,7 +62,8 @@ void recordMetrics(const SelectionTree &S, const LangOptions &Lang) { } // Return the range covering a node and all its children. -SourceRange getSourceRange(const DynTypedNode &N) { +SourceRange getSourceRange(const DynTypedNode &N, + bool IncludeQualifier = false) { // MemberExprs to implicitly access anonymous fields should not claim any // tokens for themselves. Given: // struct A { struct { int b; }; }; @@ -80,7 +81,7 @@ SourceRange getSourceRange(const DynTypedNode &N) { ? getSourceRange(DynTypedNode::create(*ME->getBase())) : SourceRange(); } - return N.getSourceRange(); + return N.getSourceRange(IncludeQualifier); } // An IntervalSet maintains a set of disjoint subranges of an array. @@ -643,8 +644,9 @@ class SelectionVisitor : public RecursiveASTVisitor { } return traverseNode(X, [&] { return Base::TraverseDecl(X); }); } - bool TraverseTypeLoc(TypeLoc X) { - return traverseNode(&X, [&] { return Base::TraverseTypeLoc(X); }); + bool TraverseTypeLoc(TypeLoc X, bool TraverseQualifier = true) { + return traverseNode( + &X, [&] { return Base::TraverseTypeLoc(X, TraverseQualifier); }); } bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &X) { return traverseNode(&X, @@ -690,7 +692,8 @@ class SelectionVisitor : public RecursiveASTVisitor { // This means we'd never see 'int' in 'const int'! Work around that here. // (The reason for the behavior is to avoid traversing the nested Type twice, // but we ignore TraverseType anyway). - bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QX) { + bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QX, + bool TraverseQualifier = true) { return traverseNode( &QX, [&] { return TraverseTypeLoc(QX.getUnqualifiedLoc()); }); } @@ -698,7 +701,7 @@ class SelectionVisitor : public RecursiveASTVisitor { return traverseNode(&PL, [&] { return Base::TraverseObjCProtocolLoc(PL); }); } // Uninteresting parts of the AST that don't have locations within them. - bool TraverseNestedNameSpecifier(NestedNameSpecifier *) { return true; } + bool TraverseNestedNameSpecifier(NestedNameSpecifier) { return true; } bool TraverseType(QualType) { return true; } // The DeclStmt for the loop variable claims to cover the whole range @@ -798,7 +801,7 @@ class SelectionVisitor : public RecursiveASTVisitor { // An optimization for a common case: nodes outside macro expansions that // don't intersect the selection may be recursively skipped. bool canSafelySkipNode(const DynTypedNode &N) { - SourceRange S = getSourceRange(N); + SourceRange S = getSourceRange(N, /*IncludeQualifier=*/true); if (auto *TL = N.get()) { // FIXME: TypeLoc::getBeginLoc()/getEndLoc() are pretty fragile // heuristics. We should consider only pruning critical TypeLoc nodes, to diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index e6d5cf7053694..2b151b1274428 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -1127,21 +1127,6 @@ class CollectExtraHighlightings return RecursiveASTVisitor::TraverseTemplateArgumentLoc(L); } - // findExplicitReferences will walk nested-name-specifiers and - // find anything that can be resolved to a Decl. However, non-leaf - // components of nested-name-specifiers which are dependent names - // (kind "Identifier") cannot be resolved to a decl, so we visit - // them here. - bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Q) { - if (NestedNameSpecifier *NNS = Q.getNestedNameSpecifier()) { - if (NNS->getKind() == NestedNameSpecifier::Identifier) - H.addToken(Q.getLocalBeginLoc(), HighlightingKind::Type) - .addModifier(HighlightingModifier::DependentName) - .addModifier(HighlightingModifier::ClassScope); - } - return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(Q); - } - private: HighlightingsBuilder &H; }; diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp new file mode 100644 index 0000000000000..dea637b9100da --- /dev/null +++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp @@ -0,0 +1,297 @@ +//===--- SymbolDocumentation.cpp ==-------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "SymbolDocumentation.h" + +#include "support/Markup.h" +#include "clang/AST/Comment.h" +#include "clang/AST/CommentCommandTraits.h" +#include "clang/AST/CommentVisitor.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace clangd { +namespace { + +std::string commandMarkerAsString(comments::CommandMarkerKind CommandMarker) { + switch (CommandMarker) { + case comments::CommandMarkerKind::CMK_At: + return "@"; + case comments::CommandMarkerKind::CMK_Backslash: + return "\\"; + } + llvm_unreachable("Unknown command marker kind"); +} + +void commandToMarkup(markup::Paragraph &Out, StringRef Command, + comments::CommandMarkerKind CommandMarker, + StringRef Args) { + Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str()); + if (!Args.empty()) { + Out.appendSpace(); + Out.appendEmphasizedText(Args.str()); + } +} +} // namespace + +class ParagraphToMarkupDocument + : public comments::ConstCommentVisitor { +public: + ParagraphToMarkupDocument(markup::Paragraph &Out, + const comments::CommandTraits &Traits) + : Out(Out), Traits(Traits) {} + + void visitParagraphComment(const comments::ParagraphComment *C) { + if (!C) + return; + + for (const auto *Child = C->child_begin(); Child != C->child_end(); + ++Child) { + visit(*Child); + } + } + + void visitTextComment(const comments::TextComment *C) { + // Always trim leading space after a newline. + StringRef Text = C->getText(); + if (LastChunkEndsWithNewline && C->getText().starts_with(' ')) + Text = Text.drop_front(); + + LastChunkEndsWithNewline = C->hasTrailingNewline(); + Out.appendText(Text.str() + (LastChunkEndsWithNewline ? "\n" : "")); + } + + void visitInlineCommandComment(const comments::InlineCommandComment *C) { + + if (C->getNumArgs() > 0) { + std::string ArgText; + for (unsigned I = 0; I < C->getNumArgs(); ++I) { + if (!ArgText.empty()) + ArgText += " "; + ArgText += C->getArgText(I); + } + + switch (C->getRenderKind()) { + case comments::InlineCommandRenderKind::Monospaced: + Out.appendCode(ArgText); + break; + case comments::InlineCommandRenderKind::Bold: + Out.appendBoldText(ArgText); + break; + case comments::InlineCommandRenderKind::Emphasized: + Out.appendEmphasizedText(ArgText); + break; + default: + commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(), + ArgText); + break; + } + } else { + if (C->getCommandName(Traits) == "n") { + // \n is a special case, it is used to create a new line. + Out.appendText(" \n"); + LastChunkEndsWithNewline = true; + return; + } + + commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(), + ""); + } + } + + void visitHTMLStartTagComment(const comments::HTMLStartTagComment *STC) { + std::string TagText = "<" + STC->getTagName().str(); + + for (unsigned I = 0; I < STC->getNumAttrs(); ++I) { + const comments::HTMLStartTagComment::Attribute &Attr = STC->getAttr(I); + TagText += " " + Attr.Name.str() + "=\"" + Attr.Value.str() + "\""; + } + + if (STC->isSelfClosing()) + TagText += " /"; + TagText += ">"; + + LastChunkEndsWithNewline = STC->hasTrailingNewline(); + Out.appendText(TagText + (LastChunkEndsWithNewline ? "\n" : "")); + } + + void visitHTMLEndTagComment(const comments::HTMLEndTagComment *ETC) { + LastChunkEndsWithNewline = ETC->hasTrailingNewline(); + Out.appendText("getTagName().str() + ">" + + (LastChunkEndsWithNewline ? "\n" : "")); + } + +private: + markup::Paragraph &Out; + const comments::CommandTraits &Traits; + + /// If true, the next leading space after a new line is trimmed. + bool LastChunkEndsWithNewline = false; +}; + +class ParagraphToString + : public comments::ConstCommentVisitor { +public: + ParagraphToString(llvm::raw_string_ostream &Out, + const comments::CommandTraits &Traits) + : Out(Out), Traits(Traits) {} + + void visitParagraphComment(const comments::ParagraphComment *C) { + if (!C) + return; + + for (const auto *Child = C->child_begin(); Child != C->child_end(); + ++Child) { + visit(*Child); + } + } + + void visitTextComment(const comments::TextComment *C) { Out << C->getText(); } + + void visitInlineCommandComment(const comments::InlineCommandComment *C) { + Out << commandMarkerAsString(C->getCommandMarker()); + Out << C->getCommandName(Traits); + if (C->getNumArgs() > 0) { + for (unsigned I = 0; I < C->getNumArgs(); ++I) + Out << " " << C->getArgText(I); + } + Out << " "; + } + + void visitHTMLStartTagComment(const comments::HTMLStartTagComment *STC) { + Out << "<" << STC->getTagName().str(); + + for (unsigned I = 0; I < STC->getNumAttrs(); ++I) { + const comments::HTMLStartTagComment::Attribute &Attr = STC->getAttr(I); + Out << " " << Attr.Name.str(); + if (!Attr.Value.str().empty()) + Out << "=\"" << Attr.Value.str() << "\""; + } + + if (STC->isSelfClosing()) + Out << " /"; + Out << ">"; + + Out << (STC->hasTrailingNewline() ? "\n" : ""); + } + + void visitHTMLEndTagComment(const comments::HTMLEndTagComment *ETC) { + Out << "getTagName().str() << ">" + << (ETC->hasTrailingNewline() ? "\n" : ""); + } + +private: + llvm::raw_string_ostream &Out; + const comments::CommandTraits &Traits; +}; + +class BlockCommentToMarkupDocument + : public comments::ConstCommentVisitor { +public: + BlockCommentToMarkupDocument(markup::Document &Out, + const comments::CommandTraits &Traits) + : Out(Out), Traits(Traits) {} + + void visitBlockCommandComment(const comments::BlockCommandComment *B) { + + switch (B->getCommandID()) { + case comments::CommandTraits::KCI_arg: + case comments::CommandTraits::KCI_li: + // \li and \arg are special cases, they are used to create a list item. + // In markdown it is a bullet list. + ParagraphToMarkupDocument(Out.addBulletList().addItem().addParagraph(), + Traits) + .visit(B->getParagraph()); + break; + default: { + // Some commands have arguments, like \throws. + // The arguments are not part of the paragraph. + // We need reconstruct them here. + std::string ArgText; + for (unsigned I = 0; I < B->getNumArgs(); ++I) { + if (!ArgText.empty()) + ArgText += " "; + ArgText += B->getArgText(I); + } + auto &P = Out.addParagraph(); + commandToMarkup(P, B->getCommandName(Traits), B->getCommandMarker(), + ArgText); + if (B->getParagraph() && !B->getParagraph()->isWhitespace()) { + // For commands with arguments, the paragraph starts after the first + // space. Therefore we need to append a space manually in this case. + if (!ArgText.empty()) + P.appendSpace(); + ParagraphToMarkupDocument(P, Traits).visit(B->getParagraph()); + } + } + } + } + + void visitVerbatimBlockComment(const comments::VerbatimBlockComment *VB) { + commandToMarkup(Out.addParagraph(), VB->getCommandName(Traits), + VB->getCommandMarker(), ""); + + std::string VerbatimText; + + for (const auto *LI = VB->child_begin(); LI != VB->child_end(); ++LI) { + if (const auto *Line = cast(*LI)) { + VerbatimText += Line->getText().str() + "\n"; + } + } + + Out.addCodeBlock(VerbatimText, ""); + + commandToMarkup(Out.addParagraph(), VB->getCloseName(), + VB->getCommandMarker(), ""); + } + + void visitVerbatimLineComment(const comments::VerbatimLineComment *VL) { + auto &P = Out.addParagraph(); + commandToMarkup(P, VL->getCommandName(Traits), VL->getCommandMarker(), ""); + P.appendSpace().appendCode(VL->getText().str(), true).appendSpace(); + } + +private: + markup::Document &Out; + const comments::CommandTraits &Traits; + StringRef CommentEscapeMarker; +}; + +void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName, + markup::Paragraph &Out) { + if (ParamName.empty()) + return; + + if (const auto *P = Parameters.lookup(ParamName)) { + ParagraphToMarkupDocument(Out, Traits).visit(P->getParagraph()); + } +} + +void SymbolDocCommentVisitor::parameterDocToString( + StringRef ParamName, llvm::raw_string_ostream &Out) { + if (ParamName.empty()) + return; + + if (const auto *P = Parameters.lookup(ParamName)) { + ParagraphToString(Out, Traits).visit(P->getParagraph()); + } +} + +void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) { + for (unsigned I = 0; I < CommentPartIndex; ++I) { + if (const auto *BC = BlockCommands.lookup(I)) { + BlockCommentToMarkupDocument(Out, Traits).visit(BC); + } else if (const auto *P = FreeParagraphs.lookup(I)) { + ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P); + } + } +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h new file mode 100644 index 0000000000000..b5120ba04e8f1 --- /dev/null +++ b/clang-tools-extra/clangd/SymbolDocumentation.h @@ -0,0 +1,155 @@ +//===--- SymbolDocumentation.h ==---------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Class to parse doxygen comments into a flat structure for consumption +// in e.g. Hover and Code Completion +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H + +#include "support/Markup.h" +#include "clang/AST/Comment.h" +#include "clang/AST/CommentLexer.h" +#include "clang/AST/CommentParser.h" +#include "clang/AST/CommentSema.h" +#include "clang/AST/CommentVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace clang { +namespace clangd { + +class SymbolDocCommentVisitor + : public comments::ConstCommentVisitor { +public: + SymbolDocCommentVisitor(comments::FullComment *FC, + const CommentOptions &CommentOpts) + : Traits(Allocator, CommentOpts), Allocator() { + if (!FC) + return; + + for (auto *Block : FC->getBlocks()) { + visit(Block); + } + } + + SymbolDocCommentVisitor(llvm::StringRef Documentation, + const CommentOptions &CommentOpts) + : Traits(Allocator, CommentOpts), Allocator() { + + if (Documentation.empty()) + return; + + CommentWithMarkers.reserve(Documentation.size() + + Documentation.count('\n') * 3); + + // The comment lexer expects doxygen markers, so add them back. + // We need to use the /// style doxygen markers because the comment could + // contain the closing the closing tag "*/" of a C Style "/** */" comment + // which would break the parsing if we would just enclose the comment text + // with "/** */". + CommentWithMarkers = "///"; + bool NewLine = true; + for (char C : Documentation) { + if (C == '\n') { + CommentWithMarkers += "\n///"; + NewLine = true; + } else { + if (NewLine && (C == '<')) { + // A comment line starting with '///<' is treated as a doxygen + // comment. Therefore add a space to separate the '<' from the comment + // marker. This allows to parse html tags at the beginning of a line + // and the escape marker prevents adding the artificial space in the + // markup documentation. The extra space will not be rendered, since + // we render it as markdown. + CommentWithMarkers += ' '; + } + CommentWithMarkers += C; + NewLine = false; + } + } + SourceManagerForFile SourceMgrForFile("mock_file.cpp", CommentWithMarkers); + + SourceManager &SourceMgr = SourceMgrForFile.get(); + // The doxygen Sema requires a Diagostics consumer, since it reports + // warnings e.g. when parameters are not documented correctly. These + // warnings are not relevant for us, so we can ignore them. + SourceMgr.getDiagnostics().setClient(new IgnoringDiagConsumer); + + comments::Sema S(Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits, + /*PP=*/nullptr); + comments::Lexer L(Allocator, SourceMgr.getDiagnostics(), Traits, + SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()), + CommentWithMarkers.data(), + CommentWithMarkers.data() + CommentWithMarkers.size()); + comments::Parser P(L, S, Allocator, SourceMgr, SourceMgr.getDiagnostics(), + Traits); + comments::FullComment *FC = P.parseFullComment(); + + if (!FC) + return; + + for (auto *Block : FC->getBlocks()) { + visit(Block); + } + } + + bool isParameterDocumented(StringRef ParamName) const { + return Parameters.contains(ParamName); + } + + void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out); + + void parameterDocToString(StringRef ParamName, llvm::raw_string_ostream &Out); + + void docToMarkup(markup::Document &Out); + + void visitBlockCommandComment(const comments::BlockCommandComment *B) { + BlockCommands[CommentPartIndex] = B; + CommentPartIndex++; + } + + void visitParagraphComment(const comments::ParagraphComment *P) { + FreeParagraphs[CommentPartIndex] = P; + CommentPartIndex++; + } + + void visitParamCommandComment(const comments::ParamCommandComment *P) { + Parameters[P->getParamNameAsWritten()] = P; + } + +private: + comments::CommandTraits Traits; + llvm::BumpPtrAllocator Allocator; + std::string CommentWithMarkers; + + /// Index to keep track of the order of the comments. + /// We want to rearange some commands like \\param. + /// This index allows us to keep the order of the other comment parts. + unsigned CommentPartIndex = 0; + + /// Parsed paragaph(s) of the "param" comamnd(s) + llvm::SmallDenseMap + Parameters; + + /// All the block commands. + llvm::SmallDenseMap + BlockCommands; + + /// All "free" text paragraphs. + llvm::SmallDenseMap + FreeParagraphs; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index 83a8b7289aec3..a253a630a48cc 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1876,7 +1876,7 @@ static void fillSubTypes(const SymbolID &ID, }); } -using RecursionProtectionSet = llvm::SmallSet; +using RecursionProtectionSet = llvm::SmallPtrSet; // Extracts parents from AST and populates the type hierarchy item. static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath, @@ -1965,7 +1965,8 @@ std::vector findRecordTypeAt(ParsedAST &AST, // Return the type most associated with an AST node. // This isn't precisely defined: we want "go to type" to do something useful. -static QualType typeForNode(const SelectionTree::Node *N) { +static QualType typeForNode(const ASTContext &Ctx, + const SelectionTree::Node *N) { // If we're looking at a namespace qualifier, walk up to what it's qualifying. // (If we're pointing at a *class* inside a NNS, N will be a TypeLoc). while (N && N->ASTNode.get()) @@ -1999,10 +2000,13 @@ static QualType typeForNode(const SelectionTree::Node *N) { if (const Decl *D = N->ASTNode.get()) { struct Visitor : ConstDeclVisitor { + const ASTContext &Ctx; + Visitor(const ASTContext &Ctx) : Ctx(Ctx) {} + QualType VisitValueDecl(const ValueDecl *D) { return D->getType(); } // Declaration of a type => that type. QualType VisitTypeDecl(const TypeDecl *D) { - return QualType(D->getTypeForDecl(), 0); + return Ctx.getTypeDeclType(D); } // Exception: alias declaration => the underlying type, not the alias. QualType VisitTypedefNameDecl(const TypedefNameDecl *D) { @@ -2012,7 +2016,7 @@ static QualType typeForNode(const SelectionTree::Node *N) { QualType VisitTemplateDecl(const TemplateDecl *D) { return Visit(D->getTemplatedDecl()); } - } V; + } V(Ctx); return V.Visit(D); } @@ -2156,7 +2160,8 @@ std::vector findType(ParsedAST &AST, Position Pos, // unique_ptr>. Let's *not* remove them, because it gives you some // information about the type you may have not known before // (since unique_ptr> != unique_ptr). - for (const QualType& Type : unwrapFindType(typeForNode(N), AST.getHeuristicResolver())) + for (const QualType &Type : unwrapFindType( + typeForNode(AST.getASTContext(), N), AST.getHeuristicResolver())) llvm::copy(locateSymbolForType(AST, Type, Index), std::back_inserter(LocatedSymbols)); diff --git a/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp b/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp index 67fc451a6a1a1..f65c74fdbc9ee 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp @@ -115,13 +115,6 @@ class UsingFinder : public RecursiveASTVisitor { const SourceManager &SM; }; -bool isFullyQualified(const NestedNameSpecifier *NNS) { - if (!NNS) - return false; - return NNS->getKind() == NestedNameSpecifier::Global || - isFullyQualified(NNS->getPrefix()); -} - struct InsertionPointData { // Location to insert the "using" statement. If invalid then the statement // should not be inserted at all (it already exists). @@ -167,18 +160,20 @@ findInsertionPoint(const Tweak::Selection &Inputs, for (auto &U : Usings) { // Only "upgrade" to fully qualified is all relevant using decls are fully // qualified. Otherwise trust what the user typed. - if (!isFullyQualified(U->getQualifier())) + if (!U->getQualifier().isFullyQualified()) AlwaysFullyQualify = false; if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc())) // "Usings" is sorted, so we're done. break; - if (const auto *Namespace = dyn_cast_if_present( - U->getQualifier()->getAsNamespace())) { + if (NestedNameSpecifier Qualifier = U->getQualifier(); + Qualifier.getKind() == NestedNameSpecifier::Kind::Namespace) { + const auto *Namespace = + U->getQualifier().getAsNamespaceAndPrefix().Namespace; if (Namespace->getCanonicalDecl() == QualifierToRemove.getNestedNameSpecifier() - ->getAsNamespace() - ->getCanonicalDecl() && + .getAsNamespaceAndPrefix() + .Namespace->getCanonicalDecl() && U->getName() == Name) { return InsertionPointData(); } @@ -232,8 +227,9 @@ findInsertionPoint(const Tweak::Selection &Inputs, } bool isNamespaceForbidden(const Tweak::Selection &Inputs, - const NestedNameSpecifier &Namespace) { - const auto *NS = dyn_cast(Namespace.getAsNamespace()); + NestedNameSpecifier Namespace) { + const auto *NS = + dyn_cast(Namespace.getAsNamespaceAndPrefix().Namespace); if (!NS) return true; std::string NamespaceStr = printNamespaceScope(*NS); @@ -247,11 +243,11 @@ bool isNamespaceForbidden(const Tweak::Selection &Inputs, return false; } -std::string getNNSLAsString(NestedNameSpecifierLoc &NNSL, +std::string getNNSLAsString(NestedNameSpecifierLoc NNSL, const PrintingPolicy &Policy) { std::string Out; llvm::raw_string_ostream OutStream(Out); - NNSL.getNestedNameSpecifier()->print(OutStream, Policy); + NNSL.getNestedNameSpecifier().print(OutStream, Policy); return OutStream.str(); } @@ -276,16 +272,15 @@ bool AddUsing::prepare(const Selection &Inputs) { continue; } if (auto *T = Node->ASTNode.get()) { - if (T->getAs()) { + // Find the outermost TypeLoc. + if (Node->Parent->ASTNode.get()) + continue; + if (isa(T->getTypePtr())) break; - } - if (Node->Parent->ASTNode.get() || - Node->Parent->ASTNode.get()) { - // Node is TypeLoc, but it's parent is either TypeLoc or - // NestedNameSpecifier. In both cases, we want to go up, to find - // the outermost TypeLoc. + // Find the outermost TypeLoc. + if (Node->Parent->ASTNode.get()) continue; - } } break; } @@ -307,32 +302,70 @@ bool AddUsing::prepare(const Selection &Inputs) { MustInsertAfterLoc = D->getDecl()->getBeginLoc(); } } else if (auto *T = Node->ASTNode.get()) { - if (auto E = T->getAs()) { - QualifierToRemove = E.getQualifierLoc(); - - SpelledNameRange = E.getSourceRange(); - if (auto T = E.getNamedTypeLoc().getAs()) { - // Remove the template arguments from the name. - SpelledNameRange.setEnd(T.getLAngleLoc().getLocWithOffset(-1)); - } - - if (const auto *ET = E.getTypePtr()) { - if (const auto *TDT = - dyn_cast(ET->getNamedType().getTypePtr())) { - MustInsertAfterLoc = TDT->getDecl()->getBeginLoc(); - } else if (auto *TD = ET->getAsTagDecl()) { - MustInsertAfterLoc = TD->getBeginLoc(); - } - } + switch (T->getTypeLocClass()) { + case TypeLoc::TemplateSpecialization: { + auto TL = T->castAs(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getTemplateNameLoc(); + if (auto *TD = TL.getTypePtr()->getTemplateName().getAsTemplateDecl( + /*IgnoreDeduced=*/true)) + MustInsertAfterLoc = TD->getBeginLoc(); + break; + } + case TypeLoc::Enum: + case TypeLoc::Record: + case TypeLoc::InjectedClassName: { + auto TL = T->castAs(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getOriginalDecl()->getBeginLoc(); + break; + } + case TypeLoc::Typedef: { + auto TL = T->castAs(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getDecl()->getBeginLoc(); + break; + } + case TypeLoc::UnresolvedUsing: { + auto TL = T->castAs(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getDecl()->getBeginLoc(); + break; + } + case TypeLoc::Using: { + auto TL = T->castAs(); + QualifierToRemove = TL.getQualifierLoc(); + if (!QualifierToRemove) + break; + SpelledNameRange = TL.getNameLoc(); + MustInsertAfterLoc = TL.getDecl()->getBeginLoc(); + break; + } + default: + break; } + if (QualifierToRemove) + SpelledNameRange.setBegin(QualifierToRemove.getBeginLoc()); } if (!QualifierToRemove || // FIXME: This only supports removing qualifiers that are made up of just // namespace names. If qualifier contains a type, we could take the // longest namespace prefix and remove that. - !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() || + QualifierToRemove.getNestedNameSpecifier().getKind() != + NestedNameSpecifier::Kind::Namespace || // Respect user config. - isNamespaceForbidden(Inputs, *QualifierToRemove.getNestedNameSpecifier())) + isNamespaceForbidden(Inputs, QualifierToRemove.getNestedNameSpecifier())) return false; // Macros are difficult. We only want to offer code action when what's spelled // under the cursor is a namespace qualifier. If it's a macro that expands to @@ -384,7 +417,7 @@ Expected AddUsing::apply(const Selection &Inputs) { llvm::raw_string_ostream UsingTextStream(UsingText); UsingTextStream << "using "; if (InsertionPoint->AlwaysFullyQualify && - !isFullyQualified(QualifierToRemove.getNestedNameSpecifier())) + !QualifierToRemove.getNestedNameSpecifier().isFullyQualified()) UsingTextStream << "::"; UsingTextStream << QualifierToSpell << SpelledName << ";" << InsertionPoint->Suffix; diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp index cd07cbf73635c..bc9a790232507 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp @@ -181,7 +181,7 @@ struct ExtractionZone { bool requiresHoisting(const SourceManager &SM, const HeuristicResolver *Resolver) const { // First find all the declarations that happened inside extraction zone. - llvm::SmallSet DeclsInExtZone; + llvm::SmallPtrSet DeclsInExtZone; for (auto *RootStmt : RootStmts) { findExplicitReferences( RootStmt, @@ -362,7 +362,7 @@ struct NewFunction { SourceLocation DefinitionPoint; std::optional ForwardDeclarationPoint; const CXXRecordDecl *EnclosingClass = nullptr; - const NestedNameSpecifier *DefinitionQualifier = nullptr; + NestedNameSpecifier DefinitionQualifier = std::nullopt; const DeclContext *SemanticDC = nullptr; const DeclContext *SyntacticDC = nullptr; const DeclContext *ForwardDeclarationSyntacticDC = nullptr; @@ -455,13 +455,12 @@ std::string NewFunction::renderQualifiers() const { } std::string NewFunction::renderDeclarationName(FunctionDeclKind K) const { - if (DefinitionQualifier == nullptr || K != OutOfLineDefinition) { + if (!DefinitionQualifier || K != OutOfLineDefinition) return Name; - } std::string QualifierName; llvm::raw_string_ostream Oss(QualifierName); - DefinitionQualifier->print(Oss, *LangOpts); + DefinitionQualifier.print(Oss, *LangOpts); return llvm::formatv("{0}{1}", QualifierName, Name); } diff --git a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp index 43cfc769f7f71..7e616968c6046 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp @@ -116,7 +116,7 @@ bool PopulateSwitch::prepare(const Selection &Sel) { EnumT = Cond->getType().getCanonicalType()->getAsAdjusted(); if (!EnumT) return false; - EnumD = EnumT->getDecl(); + EnumD = EnumT->getOriginalDecl(); if (!EnumD || EnumD->isDependentType()) return false; diff --git a/clang-tools-extra/clangd/support/Markup.cpp b/clang-tools-extra/clangd/support/Markup.cpp index a13083026f26b..89bdc656d440f 100644 --- a/clang-tools-extra/clangd/support/Markup.cpp +++ b/clang-tools-extra/clangd/support/Markup.cpp @@ -363,7 +363,12 @@ class CodeBlock : public Block { void renderMarkdown(llvm::raw_ostream &OS) const override { std::string Marker = getMarkerForCodeBlock(Contents); // No need to pad from previous blocks, as they should end with a new line. - OS << Marker << Language << '\n' << Contents << '\n' << Marker << '\n'; + OS << Marker << Language << '\n' << Contents; + if (!Contents.empty() && Contents.back() != '\n') + OS << '\n'; + // Always end with an empty line to separate code blocks from following + // paragraphs. + OS << Marker << "\n\n"; } void renderPlainText(llvm::raw_ostream &OS) const override { diff --git a/clang-tools-extra/clangd/test/modules_no_cdb.test b/clang-tools-extra/clangd/test/modules_no_cdb.test new file mode 100644 index 0000000000000..8f92be2c7b3f3 --- /dev/null +++ b/clang-tools-extra/clangd/test/modules_no_cdb.test @@ -0,0 +1,66 @@ +# A smoke test to check that clangd works without compilation database +# +# Windows have different escaping modes. +# FIXME: We should add one for windows. +# UNSUPPORTED: system-windows +# +# RUN: rm -fr %t +# RUN: mkdir -p %t +# RUN: split-file %s %t +# +# RUN: sed -e "s|DIR|%/t|g" %t/definition.jsonrpc.tmpl > %t/definition.jsonrpc +# +# RUN: clangd -experimental-modules-support -lit-test < %t/definition.jsonrpc \ +# RUN: | FileCheck -strict-whitespace %t/definition.jsonrpc + +#--- A.h +void printA(); + +#--- Use.cpp +#include "A.h" +void foo() { + print +} + +#--- definition.jsonrpc.tmpl +{ + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "processId": 123, + "rootPath": "clangd", + "capabilities": { + "textDocument": { + "completion": { + "completionItem": { + "snippetSupport": true + } + } + } + }, + "trace": "off" + } +} +--- +{ + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "file://DIR/Use.cpp", + "languageId": "cpp", + "version": 1, + "text": "#include \"A.h\"\nvoid foo() {\n print\n}\n" + } + } +} + +# CHECK: "message"{{.*}}printA{{.*}}(fix available) + +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file://DIR/Use.cpp"},"context":{"triggerKind":1},"position":{"line":2,"character":6}}} +--- +{"jsonrpc":"2.0","id":2,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp index d0bc3c4d7db98..76d46bad82224 100644 --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -421,7 +421,7 @@ TEST(ClangdAST, GetQualification) { { R"cpp( namespace ns1 { namespace ns2 { void Foo(); } } - void insert(); // ns2::Foo + void insert(); // ns1::ns2::Foo namespace ns1 { void insert(); // ns2::Foo namespace ns2 { @@ -429,7 +429,7 @@ TEST(ClangdAST, GetQualification) { } } )cpp", - {"ns2::", "ns2::", ""}, + {"ns1::ns2::", "ns2::", ""}, {"ns1::"}, }, { @@ -531,7 +531,8 @@ TEST(ClangdAST, PrintType) { ASSERT_EQ(InsertionPoints.size(), Case.Types.size()); for (size_t I = 0, E = InsertionPoints.size(); I != E; ++I) { const auto *DC = InsertionPoints[I]; - EXPECT_EQ(printType(AST.getASTContext().getTypeDeclType(TargetDecl), *DC), + EXPECT_EQ(printType(AST.getASTContext().getTypeDeclType(TargetDecl), *DC, + /*Placeholder=*/"", /*FullyQualify=*/true), Case.Types[I]); } } diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt index d425070c7f3b7..9656eeaeb37ce 100644 --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -92,6 +92,7 @@ add_unittest(ClangdUnitTests ClangdTests SourceCodeTests.cpp StdLibTests.cpp SymbolCollectorTests.cpp + SymbolDocumentationTests.cpp SymbolInfoTests.cpp SyncAPI.cpp TUSchedulerTests.cpp diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 61bd6318b46cf..7640569128172 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -938,7 +938,7 @@ TEST(CompletionTest, IncludeInsertionRespectsQuotedAngledConfig) { { Config C; C.Style.AngledHeaders.push_back( - [](auto header) { return header == "bar.h"; }); + [](auto header) { return header.contains("bar.h"); }); WithContextValue WithCfg(Config::Key, std::move(C)); Results = completions(TU, Test.point(), {Sym}); EXPECT_THAT(Results.Completions, @@ -947,7 +947,7 @@ TEST(CompletionTest, IncludeInsertionRespectsQuotedAngledConfig) { { Config C; C.Style.QuotedHeaders.push_back( - [](auto header) { return header == "bar.h"; }); + [](auto header) { return header.contains("bar.h"); }); WithContextValue WithCfg(Config::Key, std::move(C)); Results = completions(TU, Test.point(), {Sym}); EXPECT_THAT(Results.Completions, @@ -4473,6 +4473,198 @@ TEST(CompletionTest, SkipExplicitObjectParameter) { snippetSuffix("")))); } } + +TEST(CompletionTest, MemberAccessInExplicitObjMemfn) { + Annotations Code(R"cpp( + struct A { + int member {}; + int memberFnA(int a); + int memberFnA(this A&, float a); + + void foo(this A& self) { + // Should not offer any members here, since + // it needs to be referenced through `self`. + mem$c1^; + // should offer all results + self.mem$c2^; + + [&]() { + // should not offer any results + mem$c3^; + }(); + } + }; + )cpp"); + + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs = {"-std=c++23"}; + + auto Preamble = TU.preamble(); + ASSERT_TRUE(Preamble); + + CodeCompleteOptions Opts{}; + + MockFS FS; + auto Inputs = TU.inputs(FS); + + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c1"), + Preamble.get(), Inputs, Opts); + + EXPECT_THAT(Result.Completions, ElementsAre()); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"), + Preamble.get(), Inputs, Opts); + + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(named("member"), + AllOf(named("memberFnA"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("memberFnA"), signature("(float a)"), + snippetSuffix("(${1:float a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"), + Preamble.get(), Inputs, Opts); + + EXPECT_THAT(Result.Completions, ElementsAre()); + } +} + +TEST(CompletionTest, ListExplicitObjectOverloads) { + Annotations Code(R"cpp( + struct S { + void foo1(int a); + void foo2(int a) const; + void foo2(this const S& self, float a); + void foo3(this const S& self, int a); + void foo4(this S& self, int a); + }; + + void S::foo1(int a) { + this->$c1^; + } + + void S::foo2(int a) const { + this->$c2^; + } + + void S::foo3(this const S& self, int a) { + self.$c3^; + } + + void S::foo4(this S& self, int a) { + self.$c4^; + } + + void test1(S s) { + s.$c5^; + } + + void test2(const S s) { + s.$c6^; + } + )cpp"); + + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs = {"-std=c++23"}; + + auto Preamble = TU.preamble(); + ASSERT_TRUE(Preamble); + + CodeCompleteOptions Opts{}; + + MockFS FS; + auto Inputs = TU.inputs(FS); + + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c1"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c4"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c5"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c6"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp index cb2c17ad4ef0d..5c857d0b8ae3e 100644 --- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp @@ -72,15 +72,14 @@ declaration: Namespace - root expression: BinaryOperator - + expression: ImplicitCast - LValueToRValue expression: DeclRef - x - specifier: TypeSpec + specifier: Type type: Record - S expression: ImplicitCast - LValueToRValue expression: Member - x expression: CXXBindTemporary expression: CXXTemporaryObject - S - type: Elaborated + type: Record - S specifier: Namespace - root:: - type: Record - S )"}, {R"cpp( namespace root { @@ -104,14 +103,13 @@ declaration: Namespace - root expression: BinaryOperator - + expression: ImplicitCast - LValueToRValue expression: DeclRef - x - specifier: TypeSpec + specifier: Type type: Record - S expression: ImplicitCast - LValueToRValue expression: Member - x expression: CXXTemporaryObject - S - type: Elaborated + type: Record - S specifier: Namespace - root:: - type: Record - S )"}, {R"cpp( namespace root { @@ -138,7 +136,7 @@ declaration: Namespace - root type: Builtin - unsigned int statement: Return expression: DependentScopeDeclRef - value - specifier: TypeSpec + specifier: Type type: TemplateTypeParm - T )"}, {R"cpp( @@ -154,8 +152,7 @@ declaration: Var - root expression: DeclRef - operator+ expression: MaterializeTemporary - lvalue expression: CXXTemporaryObject - Foo - type: Elaborated - type: Record - Foo + type: Record - Foo expression: IntegerLiteral - 42 )"}, {R"cpp( diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp index 4d77f9d690ca0..f369e1b0341e8 100644 --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -731,6 +731,12 @@ TEST_F(TargetDeclTest, BuiltinTemplates) { using type_pack_element = [[__type_pack_element]]; )cpp"; EXPECT_DECLS("TemplateSpecializationTypeLoc", ); + + Code = R"cpp( + template