diff --git a/.ci/compute_projects.py b/.ci/compute_projects.py index 17a2136a270d..80e4e0221b3d 100644 --- a/.ci/compute_projects.py +++ b/.ci/compute_projects.py @@ -49,15 +49,41 @@ }, "lld": {"bolt", "cross-project-tests"}, # TODO(issues/132795): LLDB should be enabled on clang changes. - "clang": {"clang-tools-extra", "compiler-rt", "cross-project-tests"}, - "clang-tools-extra": {"libc"}, + "clang": {"clang-tools-extra", "cross-project-tests"}, "mlir": {"flang"}, # Test everything if ci scripts are changed. - # FIXME: Figure out what is missing and add here. - ".ci": {"llvm", "clang", "lld", "lldb"}, + ".ci": { + "llvm", + "clang", + "lld", + "lldb", + "bolt", + "clang-tools-extra", + "mlir", + "polly", + "flang", + "libclc", + "openmp", + }, } -DEPENDENT_RUNTIMES_TO_TEST = {"clang": {"libcxx", "libcxxabi", "libunwind"}} +# This mapping describes runtimes that should be enabled for a specific project, +# but not necessarily run for testing. The only case of this currently is lldb +# which needs some runtimes enabled for tests. +DEPENDENT_RUNTIMES_TO_BUILD = {"lldb": {"libcxx", "libcxxabi", "libunwind"}} + +# This mapping describes runtimes that should be tested when the key project is +# touched. +DEPENDENT_RUNTIMES_TO_TEST = { + "clang": {"compiler-rt"}, + "clang-tools-extra": {"libc"}, + ".ci": {"compiler-rt", "libc"}, +} +DEPENDENT_RUNTIMES_TO_TEST_NEEDS_RECONFIG = { + "llvm": {"libcxx", "libcxxabi", "libunwind"}, + "clang": {"libcxx", "libcxxabi", "libunwind"}, + ".ci": {"libcxx", "libcxxabi", "libunwind"}, +} EXCLUDE_LINUX = { "cross-project-tests", # TODO(issues/132796): Tests are failing. @@ -86,9 +112,6 @@ "cross-project-tests", "flang", "libc", - "libcxx", - "libcxxabi", - "libunwind", "lldb", "openmp", "polly", @@ -115,21 +138,35 @@ "polly": "check-polly", } -RUNTIMES = {"libcxx", "libcxxabi", "libunwind"} +RUNTIMES = {"libcxx", "libcxxabi", "libunwind", "compiler-rt", "libc"} -def _add_dependencies(projects: Set[str]) -> Set[str]: +def _add_dependencies(projects: Set[str], runtimes: Set[str]) -> Set[str]: projects_with_dependents = set(projects) current_projects_count = 0 while current_projects_count != len(projects_with_dependents): current_projects_count = len(projects_with_dependents) for project in list(projects_with_dependents): - if project not in PROJECT_DEPENDENCIES: - continue - projects_with_dependents.update(PROJECT_DEPENDENCIES[project]) + if project in PROJECT_DEPENDENCIES: + projects_with_dependents.update(PROJECT_DEPENDENCIES[project]) + for runtime in runtimes: + if runtime in PROJECT_DEPENDENCIES: + projects_with_dependents.update(PROJECT_DEPENDENCIES[runtime]) return projects_with_dependents +def _exclude_projects(current_projects: Set[str], platform: str) -> Set[str]: + if platform == "Linux": + to_exclude = EXCLUDE_LINUX + elif platform == "Windows": + to_exclude = EXCLUDE_WINDOWS + elif platform == "Darwin": + to_exclude = EXCLUDE_MAC + else: + raise ValueError(f"Unexpected platform: {platform}") + return current_projects.difference(to_exclude) + + def _compute_projects_to_test(modified_projects: Set[str], platform: str) -> Set[str]: projects_to_test = set() for modified_project in modified_projects: @@ -147,50 +184,52 @@ def _compute_projects_to_test(modified_projects: Set[str], platform: str) -> Set ): continue projects_to_test.add(dependent_project) - if platform == "Linux": - for to_exclude in EXCLUDE_LINUX: - if to_exclude in projects_to_test: - projects_to_test.remove(to_exclude) - elif platform == "Windows": - for to_exclude in EXCLUDE_WINDOWS: - if to_exclude in projects_to_test: - projects_to_test.remove(to_exclude) - elif platform == "Darwin": - for to_exclude in EXCLUDE_MAC: - if to_exclude in projects_to_test: - projects_to_test.remove(to_exclude) - else: - raise ValueError("Unexpected platform.") + projects_to_test = _exclude_projects(projects_to_test, platform) return projects_to_test -def _compute_projects_to_build(projects_to_test: Set[str]) -> Set[str]: - return _add_dependencies(projects_to_test) +def _compute_projects_to_build( + projects_to_test: Set[str], runtimes: Set[str] +) -> Set[str]: + return _add_dependencies(projects_to_test, runtimes) def _compute_project_check_targets(projects_to_test: Set[str]) -> Set[str]: check_targets = set() for project_to_test in projects_to_test: - if project_to_test not in PROJECT_CHECK_TARGETS: - continue - check_targets.add(PROJECT_CHECK_TARGETS[project_to_test]) + if project_to_test in PROJECT_CHECK_TARGETS: + check_targets.add(PROJECT_CHECK_TARGETS[project_to_test]) return check_targets -def _compute_runtimes_to_test(projects_to_test: Set[str]) -> Set[str]: +def _compute_runtimes_to_test(modified_projects: Set[str], platform: str) -> Set[str]: runtimes_to_test = set() - for project_to_test in projects_to_test: - if project_to_test not in DEPENDENT_RUNTIMES_TO_TEST: - continue - runtimes_to_test.update(DEPENDENT_RUNTIMES_TO_TEST[project_to_test]) - return runtimes_to_test + for modified_project in modified_projects: + if modified_project in DEPENDENT_RUNTIMES_TO_TEST: + runtimes_to_test.update(DEPENDENT_RUNTIMES_TO_TEST[modified_project]) + return _exclude_projects(runtimes_to_test, platform) -def _compute_runtime_check_targets(runtimes_to_test: Set[str]) -> Set[str]: - check_targets = set() - for runtime_to_test in runtimes_to_test: - check_targets.add(PROJECT_CHECK_TARGETS[runtime_to_test]) - return check_targets +def _compute_runtimes_to_test_needs_reconfig( + modified_projects: Set[str], platform: str +) -> Set[str]: + runtimes_to_test = set() + for modified_project in modified_projects: + if modified_project in DEPENDENT_RUNTIMES_TO_TEST_NEEDS_RECONFIG: + runtimes_to_test.update( + DEPENDENT_RUNTIMES_TO_TEST_NEEDS_RECONFIG[modified_project] + ) + return _exclude_projects(runtimes_to_test, platform) + + +def _compute_runtimes_to_build( + runtimes_to_test: Set[str], modified_projects: Set[str], platform: str +) -> Set[str]: + runtimes_to_build = set(runtimes_to_test) + for modified_project in modified_projects: + if modified_project in DEPENDENT_RUNTIMES_TO_BUILD: + runtimes_to_build.update(DEPENDENT_RUNTIMES_TO_BUILD[modified_project]) + return _exclude_projects(runtimes_to_build, platform) def _get_modified_projects(modified_files: list[str]) -> Set[str]: @@ -214,10 +253,19 @@ def _get_modified_projects(modified_files: list[str]) -> Set[str]: def get_env_variables(modified_files: list[str], platform: str) -> Set[str]: modified_projects = _get_modified_projects(modified_files) projects_to_test = _compute_projects_to_test(modified_projects, platform) - projects_to_build = _compute_projects_to_build(projects_to_test) + runtimes_to_test = _compute_runtimes_to_test(modified_projects, platform) + runtimes_to_test_needs_reconfig = _compute_runtimes_to_test_needs_reconfig( + modified_projects, platform + ) + runtimes_to_build = _compute_runtimes_to_build( + runtimes_to_test | runtimes_to_test_needs_reconfig, modified_projects, platform + ) + projects_to_build = _compute_projects_to_build(projects_to_test, runtimes_to_build) projects_check_targets = _compute_project_check_targets(projects_to_test) - runtimes_to_test = _compute_runtimes_to_test(projects_to_test) - runtimes_check_targets = _compute_runtime_check_targets(runtimes_to_test) + runtimes_check_targets = _compute_project_check_targets(runtimes_to_test) + runtimes_check_targets_needs_reconfig = _compute_project_check_targets( + runtimes_to_test_needs_reconfig + ) # We use a semicolon to separate the projects/runtimes as they get passed # to the CMake invocation and thus we need to use the CMake list separator # (;). We use spaces to separate the check targets as they end up getting @@ -225,8 +273,11 @@ def get_env_variables(modified_files: list[str], platform: str) -> Set[str]: return { "projects_to_build": ";".join(sorted(projects_to_build)), "project_check_targets": " ".join(sorted(projects_check_targets)), - "runtimes_to_build": ";".join(sorted(runtimes_to_test)), + "runtimes_to_build": ";".join(sorted(runtimes_to_build)), "runtimes_check_targets": " ".join(sorted(runtimes_check_targets)), + "runtimes_check_targets_needs_reconfig": " ".join( + sorted(runtimes_check_targets_needs_reconfig) + ), } diff --git a/.ci/compute_projects_test.py b/.ci/compute_projects_test.py index 885f1dd6af84..5efac2636698 100644 --- a/.ci/compute_projects_test.py +++ b/.ci/compute_projects_test.py @@ -26,6 +26,10 @@ def test_llvm(self): ) self.assertEqual( env_variables["runtimes_check_targets"], + "", + ) + self.assertEqual( + env_variables["runtimes_check_targets_needs_reconfig"], "check-cxx check-cxxabi check-unwind", ) @@ -46,6 +50,10 @@ def test_llvm_windows(self): ) self.assertEqual( env_variables["runtimes_check_targets"], + "", + ) + self.assertEqual( + env_variables["runtimes_check_targets_needs_reconfig"], "check-cxx check-cxxabi check-unwind", ) @@ -66,6 +74,10 @@ def test_llvm_mac(self): ) self.assertEqual( env_variables["runtimes_check_targets"], + "", + ) + self.assertEqual( + env_variables["runtimes_check_targets_needs_reconfig"], "check-cxx check-cxxabi check-unwind", ) @@ -75,17 +87,21 @@ def test_clang(self): ) self.assertEqual( env_variables["projects_to_build"], - "clang;clang-tools-extra;compiler-rt;lld;llvm", + "clang;clang-tools-extra;lld;llvm", ) self.assertEqual( env_variables["project_check_targets"], - "check-clang check-clang-tools check-compiler-rt", + "check-clang check-clang-tools", ) self.assertEqual( - env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind" + env_variables["runtimes_to_build"], "compiler-rt;libcxx;libcxxabi;libunwind" ) self.assertEqual( env_variables["runtimes_check_targets"], + "check-compiler-rt", + ) + self.assertEqual( + env_variables["runtimes_check_targets_needs_reconfig"], "check-cxx check-cxxabi check-unwind", ) @@ -104,6 +120,10 @@ def test_clang_windows(self): ) self.assertEqual( env_variables["runtimes_check_targets"], + "", + ) + self.assertEqual( + env_variables["runtimes_check_targets_needs_reconfig"], "check-cxx check-cxxabi check-unwind", ) @@ -115,6 +135,7 @@ def test_bolt(self): self.assertEqual(env_variables["project_check_targets"], "check-bolt") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_lldb(self): env_variables = compute_projects.get_env_variables( @@ -124,6 +145,7 @@ def test_lldb(self): self.assertEqual(env_variables["project_check_targets"], "check-lldb") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_mlir(self): env_variables = compute_projects.get_env_variables( @@ -135,6 +157,7 @@ def test_mlir(self): ) self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_flang(self): env_variables = compute_projects.get_env_variables( @@ -144,6 +167,7 @@ def test_flang(self): self.assertEqual(env_variables["project_check_targets"], "check-flang") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_invalid_subproject(self): env_variables = compute_projects.get_env_variables( @@ -153,6 +177,7 @@ def test_invalid_subproject(self): self.assertEqual(env_variables["project_check_targets"], "") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_top_level_file(self): env_variables = compute_projects.get_env_variables(["README.md"], "Linux") @@ -160,6 +185,7 @@ def test_top_level_file(self): self.assertEqual(env_variables["project_check_targets"], "") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_exclude_runtiems_in_projects(self): env_variables = compute_projects.get_env_variables( @@ -169,6 +195,7 @@ def test_exclude_runtiems_in_projects(self): self.assertEqual(env_variables["project_check_targets"], "") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_exclude_docs(self): env_variables = compute_projects.get_env_variables( @@ -178,6 +205,7 @@ def test_exclude_docs(self): self.assertEqual(env_variables["project_check_targets"], "") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_exclude_gn(self): env_variables = compute_projects.get_env_variables( @@ -187,24 +215,57 @@ def test_exclude_gn(self): self.assertEqual(env_variables["project_check_targets"], "") self.assertEqual(env_variables["runtimes_to_build"], "") self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") def test_ci(self): env_variables = compute_projects.get_env_variables( [".ci/compute_projects.py"], "Linux" ) - self.assertEqual(env_variables["projects_to_build"], "clang;lld;lldb;llvm") + self.assertEqual( + env_variables["projects_to_build"], + "bolt;clang;clang-tools-extra;flang;libclc;lld;lldb;llvm;mlir;polly", + ) self.assertEqual( env_variables["project_check_targets"], - "check-clang check-lld check-lldb check-llvm", + "check-bolt check-clang check-clang-tools check-flang check-lld check-lldb check-llvm check-mlir check-polly", ) self.assertEqual( - env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind" + env_variables["runtimes_to_build"], + "compiler-rt;libc;libcxx;libcxxabi;libunwind", ) self.assertEqual( env_variables["runtimes_check_targets"], + "check-compiler-rt check-libc", + ) + self.assertEqual( + env_variables["runtimes_check_targets_needs_reconfig"], "check-cxx check-cxxabi check-unwind", ) + def test_lldb(self): + env_variables = compute_projects.get_env_variables( + ["lldb/CMakeLists.txt"], "Linux" + ) + self.assertEqual(env_variables["projects_to_build"], "clang;lldb;llvm") + self.assertEqual(env_variables["project_check_targets"], "check-lldb") + self.assertEqual( + env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind" + ) + self.assertEqual(env_variables["runtimes_check_targets"], "") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") + + def test_clang_tools_extra(self): + env_variables = compute_projects.get_env_variables( + ["clang-tools-extra/CMakeLists.txt"], "Linux" + ) + self.assertEqual( + env_variables["projects_to_build"], "clang;clang-tools-extra;lld;llvm" + ) + self.assertEqual(env_variables["project_check_targets"], "check-clang-tools") + self.assertEqual(env_variables["runtimes_to_build"], "libc") + self.assertEqual(env_variables["runtimes_check_targets"], "check-libc") + self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "") + if __name__ == "__main__": unittest.main() diff --git a/.ci/generate-buildkite-pipeline-premerge b/.ci/generate-buildkite-pipeline-premerge deleted file mode 100755 index 5e5f916f35b7..000000000000 --- a/.ci/generate-buildkite-pipeline-premerge +++ /dev/null @@ -1,131 +0,0 @@ -#!/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 file generates a Buildkite pipeline that triggers the various CI jobs for -# the LLVM project during pre-commit CI. -# -# See https://buildkite.com/docs/agent/v3/cli-pipeline#pipeline-format. -# -# As this outputs a yaml file, it's possible to log messages to stderr or -# prefix with "#". - - -set -eu -set -o pipefail - -# Environment variables script works with: - -# Set by buildkite -: ${BUILDKITE_PULL_REQUEST_BASE_BRANCH:=} -: ${BUILDKITE_COMMIT:=} -: ${BUILDKITE_BRANCH:=} -# Fetch origin to have an up to date merge base for the diff. -git fetch origin -# List of files affected by this commit -: ${MODIFIED_FILES:=$(git diff --name-only origin/${BUILDKITE_PULL_REQUEST_BASE_BRANCH}...HEAD)} -# Filter rules for generic windows tests -: ${WINDOWS_AGENTS:='{"queue": "windows"}'} -# Filter rules for generic linux tests -: ${LINUX_AGENTS:='{"queue": "linux"}'} - -reviewID="$(git log --format=%B -n 1 | sed -nE 's/^Review-ID:[[:space:]]*(.+)$/\1/p')" -if [[ "${reviewID}" != "" ]]; then - buildMessage="https://llvm.org/${reviewID}" -else - buildMessage="Push to branch ${BUILDKITE_BRANCH}" -fi - -cat <&2 -echo "$MODIFIED_FILES" >&2 -modified_dirs=$(echo "$MODIFIED_FILES" | cut -d'/' -f1 | sort -u) -echo "Directories modified:" >&2 -echo "$modified_dirs" >&2 - -# Project specific pipelines. - -# If libc++ or one of the runtimes directories changed. -if echo "$modified_dirs" | grep -q -E "^(libcxx|libcxxabi|libunwind|runtimes|cmake)$"; then - cat < list[dict[str, str]]: - """Fetches a page of the build list using the GraphQL BuildKite API. - - Returns the BUILDKITE_GRAPHQL_BUILDS_PER_PAGE last running/queued builds, - or the BUILDKITE_GRAPHQL_BUILDS_PER_PAGE running/queued builds - older than the one pointer by |after_cursor| if provided. - The |after_cursor| value is taken from the previous page returned by the - API. - - Args: - buildkite_token: the secret token to authenticate GraphQL requests. - after_cursor: cursor after which to start the page fetch. - - Returns: - The most recent builds after cursor (if set) with the following format: - [ - { - "cursor": , - "number": , - } - ] - """ - - BUILDKITE_GRAPHQL_QUERY = """ - query OrganizationShowQuery {{ - organization(slug: "llvm-project") {{ - pipelines(search: "Github pull requests", first: 1) {{ - edges {{ - node {{ - builds (state: [CANCELING, CREATING, FAILING, RUNNING], first: {PAGE_SIZE}, after: {AFTER}) {{ - edges {{ - cursor - node {{ - number - }} - }} - }} - }} - }} - }} - }} - }} - """ - query = BUILDKITE_GRAPHQL_QUERY.format( - PAGE_SIZE=BUILDKITE_GRAPHQL_BUILDS_PER_PAGE, - AFTER="null" if after_cursor is None else '"{}"'.format(after_cursor), - ) - query = json.dumps({"query": query}) - url = "https://graphql.buildkite.com/v1" - headers = { - "Authorization": "Bearer " + buildkite_token, - "Content-Type": "application/json", - } - data = requests.post(url, data=query, headers=headers).json() - # De-nest the build list. - if "errors" in data: - logging.info("Failed to fetch BuildKite jobs: {}".format(data["errors"])) - return [] - builds = data["data"]["organization"]["pipelines"]["edges"][0]["node"]["builds"][ - "edges" - ] - # Fold cursor info into the node dictionnary. - return [{**x["node"], "cursor": x["cursor"]} for x in builds] - - -def buildkite_get_build_info(build_number: str) -> dict: - """Returns all the info associated with the provided build number. - - Note: for unknown reasons, graphql returns no jobs for a given build, - while this endpoint does, hence why this uses this API instead of graphql. - - Args: - build_number: which build number to fetch info for. - - Returns: - The info for the target build, a JSON dictionnary. - """ - - URL = "https://buildkite.com/llvm-project/github-pull-requests/builds/{}.json" - return requests.get(URL.format(build_number)).json() - - -def buildkite_get_incomplete_tasks(buildkite_token: str) -> list: - """Returns all the running/pending BuildKite builds. - - Args: - buildkite_token: the secret token to authenticate GraphQL requests. - last_cursor: the cursor to stop at if set. If None, a full page is fetched. - """ - output = [] - cursor = None - while True: - page = buildkite_fetch_page_build_list(buildkite_token, cursor) - if len(page) == 0: - break - cursor = page[-1]["cursor"] - output += page - return output - - -def buildkite_get_metrics( - buildkite_token: str, previously_incomplete: set[int] -) -> (list[JobMetrics], set[int]): - """Returns a tuple with: - - - the metrics recorded for newly completed workflow jobs. - - the set of workflow still running now. - - Args: - buildkite_token: the secret token to authenticate GraphQL requests. - previously_incomplete: the set of running workflows the last time this - function was called. - """ - - running_builds = buildkite_get_incomplete_tasks(buildkite_token) - incomplete_now = set([x["number"] for x in running_builds]) - output = [] - - for build_id in previously_incomplete: - if build_id in incomplete_now: - continue - - info = buildkite_get_build_info(build_id) - metric_timestamp = dateutil.parser.isoparse(info["finished_at"]) - for job in info["jobs"]: - # This workflow is not interesting to us. - if job["name"] not in BUILDKITE_WORKFLOW_TO_TRACK: - continue - - # Don't count canceled jobs. - if job["canceled_at"]: - continue - - created_at = dateutil.parser.isoparse(job["created_at"]) - scheduled_at = dateutil.parser.isoparse(job["scheduled_at"]) - started_at = dateutil.parser.isoparse(job["started_at"]) - finished_at = dateutil.parser.isoparse(job["finished_at"]) - - job_name = BUILDKITE_WORKFLOW_TO_TRACK[job["name"]] - queue_time = (started_at - scheduled_at).seconds - run_time = (finished_at - started_at).seconds - status = bool(job["passed"]) - - # Grafana will refuse to ingest metrics older than ~2 hours, so we - # should avoid sending historical data. - metric_age_mn = ( - datetime.datetime.now(datetime.timezone.utc) - metric_timestamp - ).total_seconds() / 60 - if metric_age_mn > GRAFANA_METRIC_MAX_AGE_MN: - logging.warning( - f"Job {job['name']} from workflow {build_id} dropped due" - + f" to staleness: {metric_age_mn}mn old." - ) - continue - - metric_timestamp_ns = int(metric_timestamp.timestamp()) * 10**9 - workflow_id = build_id - workflow_name = "Github pull requests" - output.append( - JobMetrics( - job_name, - queue_time, - run_time, - status, - metric_timestamp_ns, - workflow_id, - workflow_name, - ) - ) - - return output, incomplete_now - - def github_get_metrics( github_repo: github.Repository, last_workflows_seen_as_completed: set[int] ) -> tuple[list[JobMetrics], int]: @@ -478,7 +288,6 @@ def upload_metrics(workflow_metrics, metrics_userid, api_key): def main(): # Authenticate with Github github_auth = Auth.Token(os.environ["GITHUB_TOKEN"]) - buildkite_token = os.environ["BUILDKITE_TOKEN"] grafana_api_key = os.environ["GRAFANA_API_KEY"] grafana_metrics_userid = os.environ["GRAFANA_METRICS_USERID"] @@ -486,9 +295,6 @@ def main(): # Because the Github queries are broken, we'll simply log a 'processed' # bit for the last COUNT_TO_PROCESS workflows. gh_last_workflows_seen_as_completed = set() - # Stores the list of pending/running builds in BuildKite we need to check - # at the next iteration. - bk_incomplete = set() # Enter the main loop. Every five minutes we wake up and dump metrics for # the relevant jobs. @@ -500,13 +306,8 @@ def main(): github_repo, gh_last_workflows_seen_as_completed ) - bk_metrics, bk_incomplete = buildkite_get_metrics( - buildkite_token, bk_incomplete - ) - - metrics = gh_metrics + bk_metrics - upload_metrics(metrics, grafana_metrics_userid, grafana_api_key) - logging.info(f"Uploaded {len(metrics)} metrics") + upload_metrics(gh_metrics, grafana_metrics_userid, grafana_api_key) + logging.info(f"Uploaded {len(gh_metrics)} metrics") time.sleep(SCRAPE_INTERVAL_SECONDS) diff --git a/.ci/monolithic-linux.sh b/.ci/monolithic-linux.sh index f5a31fa45a64..89447963b852 100755 --- a/.ci/monolithic-linux.sh +++ b/.ci/monolithic-linux.sh @@ -42,19 +42,17 @@ function at-exit { # If building fails there will be no results files. shopt -s nullglob - if command -v buildkite-agent 2>&1 >/dev/null - then - python3 "${MONOREPO_ROOT}"/.ci/generate_test_report_buildkite.py ":linux: Linux x64 Test Results" \ - "linux-x64-test-results" $retcode "${BUILD_DIR}"/test-results.*.xml - else - python3 "${MONOREPO_ROOT}"/.ci/generate_test_report_github.py ":penguin: Linux x64 Test Results" \ - $retcode "${BUILD_DIR}"/test-results.*.xml >> $GITHUB_STEP_SUMMARY - fi + + 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}" +runtime_targets="${4}" +runtime_targets_needs_reconfig="${5}" lit_args="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --use-unique-output-file-name --timeout=1200 --time-tests" @@ -70,7 +68,7 @@ export LLVM_SYMBOLIZER_PATH=`which llvm-symbolizer` # It will not be built unless it is used. cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \ -D LLVM_ENABLE_PROJECTS="${projects}" \ - -D LLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \ + -D LLVM_ENABLE_RUNTIMES="${runtimes}" \ -G Ninja \ -D CMAKE_PREFIX_PATH="${HOME}/.local" \ -D CMAKE_BUILD_TYPE=Release \ @@ -91,62 +89,34 @@ echo "--- ninja" # Targets are not escaped as they are passed as separate arguments. ninja -C "${BUILD_DIR}" -k 0 ${targets} -runtimes="${3}" -runtime_targets="${4}" +if [[ "${runtime_targets}" != "" ]]; then + echo "--- ninja runtimes" + + ninja -C "${BUILD_DIR}" ${runtime_targets} +fi # Compiling runtimes with just-built Clang and running their tests # as an additional testing for Clang. -if [[ "${runtimes}" != "" ]]; then - if [[ "${runtime_targets}" == "" ]]; then - echo "Runtimes to build are specified, but targets are not." - exit 1 - fi - - echo "--- ninja install-clang" - - ninja -C ${BUILD_DIR} install-clang install-clang-resource-headers - - RUNTIMES_BUILD_DIR="${MONOREPO_ROOT}/build-runtimes" - INSTALL_DIR="${BUILD_DIR}/install" - mkdir -p ${RUNTIMES_BUILD_DIR} - +if [[ "${runtime_targets_needs_reconfig}" != "" ]]; then echo "--- cmake runtimes C++26" - rm -rf "${RUNTIMES_BUILD_DIR}" - cmake -S "${MONOREPO_ROOT}/runtimes" -B "${RUNTIMES_BUILD_DIR}" -GNinja \ - -D CMAKE_C_COMPILER="${INSTALL_DIR}/bin/clang" \ - -D CMAKE_CXX_COMPILER="${INSTALL_DIR}/bin/clang++" \ - -D LLVM_ENABLE_RUNTIMES="${runtimes}" \ - -D LIBCXX_CXX_ABI=libcxxabi \ - -D CMAKE_BUILD_TYPE=RelWithDebInfo \ - -D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ - -D LIBCXX_TEST_PARAMS="std=c++26" \ - -D LIBCXXABI_TEST_PARAMS="std=c++26" \ - -D LLVM_LIT_ARGS="${lit_args}" + cmake \ + -D LIBCXX_TEST_PARAMS="std=c++26" \ + -D LIBCXXABI_TEST_PARAMS="std=c++26" \ + "${BUILD_DIR}" echo "--- ninja runtimes C++26" - ninja -vC "${RUNTIMES_BUILD_DIR}" ${runtime_targets} + ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} echo "--- cmake runtimes clang modules" - # We don't need to do a clean build of runtimes, because LIBCXX_TEST_PARAMS - # and LIBCXXABI_TEST_PARAMS only affect lit configuration, which successfully - # propagates without a clean build. Other that those two variables, builds - # are supposed to be the same. - - cmake -S "${MONOREPO_ROOT}/runtimes" -B "${RUNTIMES_BUILD_DIR}" -GNinja \ - -D CMAKE_C_COMPILER="${INSTALL_DIR}/bin/clang" \ - -D CMAKE_CXX_COMPILER="${INSTALL_DIR}/bin/clang++" \ - -D LLVM_ENABLE_RUNTIMES="${runtimes}" \ - -D LIBCXX_CXX_ABI=libcxxabi \ - -D CMAKE_BUILD_TYPE=RelWithDebInfo \ - -D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ - -D LIBCXX_TEST_PARAMS="enable_modules=clang" \ - -D LIBCXXABI_TEST_PARAMS="enable_modules=clang" \ - -D LLVM_LIT_ARGS="${lit_args}" + cmake \ + -D LIBCXX_TEST_PARAMS="enable_modules=clang" \ + -D LIBCXXABI_TEST_PARAMS="enable_modules=clang" \ + "${BUILD_DIR}" echo "--- ninja runtimes clang modules" - ninja -vC "${RUNTIMES_BUILD_DIR}" ${runtime_targets} + ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} fi diff --git a/.ci/monolithic-windows.sh b/.ci/monolithic-windows.sh index a0997420b0d3..dc2913830e92 100755 --- a/.ci/monolithic-windows.sh +++ b/.ci/monolithic-windows.sh @@ -37,14 +37,9 @@ function at-exit { # If building fails there will be no results files. shopt -s nullglob - if command -v buildkite-agent 2>&1 >/dev/null - then - python "${MONOREPO_ROOT}"/.ci/generate_test_report_buildkite.py ":windows: Windows x64 Test Results" \ - "windows-x64-test-results" $retcode "${BUILD_DIR}"/test-results.*.xml - else - python "${MONOREPO_ROOT}"/.ci/generate_test_report_github.py ":window: Windows x64 Test Results" \ - $retcode "${BUILD_DIR}"/test-results.*.xml >> $GITHUB_STEP_SUMMARY - fi + + 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 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 30d9f6b883ce..01569c619f8f 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -100,3 +100,9 @@ d33bf2e9df578ff7e44fd22504d6ad5a122b7ee6 # [lldb][NFC] clang-format MainLoopPosix.cpp 66bdbfbaa08fa3d8e64a7fe136a8fb717f5cdbb7 + +# [clang-tidy][NFC] Run clang-format on "clang-tools-extra/clang-tidy" +65d66625b3e2b8322ed99d82edabecbafcd0885b +ce46adb8b7ce645353eccaedf31ed9765dab77bb +68070f908bb7ac5f0b5fa9722caa504ecf723f6b +5213c57cb1f0d78aad9a253b7f6a2b62ff4c7859 diff --git a/.github/new-prs-labeler.yml b/.github/new-prs-labeler.yml index 162161ff13fb..2f8d5745668d 100644 --- a/.github/new-prs-labeler.yml +++ b/.github/new-prs-labeler.yml @@ -777,6 +777,10 @@ backend:NVPTX: - 'llvm/**/*nvptx*/**' - 'llvm/**/*NVPTX*/**' +backend:MIPS: + - '**/*mips*' + - '**/*Mips*' + backend:RISC-V: - clang/**/*riscv* - clang/**/*RISCV* diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a0ae4e2fade6..f746c4fc93b7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -205,7 +205,7 @@ jobs: steps.docs-changed-subprojects.outputs.workflow_any_changed == 'true' run: | cmake -B flang-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;mlir;flang" -DLLVM_ENABLE_SPHINX=ON ./llvm - TZ=UTC ninja -C flang-build docs-flang-html + TZ=UTC ninja -C flang-build docs-flang-html docs-flang-man mkdir built-docs/flang cp -r flang-build/docs/* built-docs/flang/ - name: Upload docs diff --git a/.github/workflows/email-check.yaml b/.github/workflows/email-check.yaml index 80c2025e3020..17b5de2ea49b 100644 --- a/.github/workflows/email-check.yaml +++ b/.github/workflows/email-check.yaml @@ -33,7 +33,8 @@ jobs: COMMENT: >- ⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off [Keep my email addresses private](https://github.com/settings/emails) setting in your account.
- See [LLVM Discourse](https://discourse.llvm.org/t/hidden-emails-on-github-should-we-do-something-about-it) for more information. + See [LLVM Developer Policy](https://llvm.org/docs/DeveloperPolicy.html#email-addresses) and + [LLVM Discourse](https://discourse.llvm.org/t/hidden-emails-on-github-should-we-do-something-about-it) for more information. run: | cat << EOF > comments [{"body" : "$COMMENT"}] diff --git a/.github/workflows/libcxx-build-and-test.yaml b/.github/workflows/libcxx-build-and-test.yaml index 80f2432b78de..f0bdf6c0b589 100644 --- a/.github/workflows/libcxx-build-and-test.yaml +++ b/.github/workflows/libcxx-build-and-test.yaml @@ -52,8 +52,8 @@ jobs: cxx: [ 'clang++-21' ] include: - config: 'generic-gcc' - cc: 'gcc-14' - cxx: 'g++-14' + cc: 'gcc-15' + cxx: 'g++-15' steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: ${{ matrix.config }}.${{ matrix.cxx }} @@ -92,8 +92,8 @@ jobs: cxx: [ 'clang++-21' ] include: - config: 'generic-gcc-cxx11' - cc: 'gcc-14' - cxx: 'g++-14' + cc: 'gcc-15' + cxx: 'g++-15' - config: 'generic-cxx26' cc: 'clang-20' cxx: 'clang++-20' diff --git a/.github/workflows/libcxx-restart-preempted-jobs.yaml b/.github/workflows/libcxx-restart-preempted-jobs.yaml index 7b341d7f22e4..9706f0459922 100644 --- a/.github/workflows/libcxx-restart-preempted-jobs.yaml +++ b/.github/workflows/libcxx-restart-preempted-jobs.yaml @@ -33,7 +33,7 @@ jobs: with: script: | const failure_regex = /Process completed with exit code 1./ - const preemption_regex = /The runner has received a shutdown signal/ + const preemption_regex = /(The runner has received a shutdown signal)|(The operation was canceled)/ const wf_run = context.payload.workflow_run core.notice(`Running on "${wf_run.display_title}" by @${wf_run.actor.login} (event: ${wf_run.event})\nWorkflow run URL: ${wf_run.html_url}`) diff --git a/.github/workflows/premerge.yaml b/.github/workflows/premerge.yaml index 0030c94d3ac0..1a10bf52c198 100644 --- a/.github/workflows/premerge.yaml +++ b/.github/workflows/premerge.yaml @@ -56,11 +56,12 @@ jobs: echo "Running project checks targets: ${project_check_targets}" echo "Building runtimes: ${runtimes_to_build}" echo "Running runtimes checks targets: ${runtimes_check_targets}" + echo "Running runtimes checks requiring reconfiguring targets: ${runtimes_check_targets_needs_reconfig}" export CC=/opt/llvm/bin/clang export CXX=/opt/llvm/bin/clang++ - ./.ci/monolithic-linux.sh "${projects_to_build}" "${project_check_targets}" "${runtimes_to_build}" "${runtimes_check_targets}" + ./.ci/monolithic-linux.sh "${projects_to_build}" "${project_check_targets}" "${runtimes_to_build}" "${runtimes_check_targets}" "${runtimes_check_targets_needs_reconfig}" - name: Upload Artifacts uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: diff --git a/bolt/include/bolt/Core/BinaryBasicBlock.h b/bolt/include/bolt/Core/BinaryBasicBlock.h index 25cccc4edecf..629f0ce8314d 100644 --- a/bolt/include/bolt/Core/BinaryBasicBlock.h +++ b/bolt/include/bolt/Core/BinaryBasicBlock.h @@ -52,9 +52,8 @@ class BinaryBasicBlock { uint64_t MispredictedCount; /// number of branches mispredicted bool operator<(const BinaryBranchInfo &Other) const { - return (Count < Other.Count) || - (Count == Other.Count && - MispredictedCount < Other.MispredictedCount); + return std::tie(Count, MispredictedCount) < + std::tie(Other.Count, Other.MispredictedCount); } }; diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h index 14957cba5017..ca8b786f4ab6 100644 --- a/bolt/include/bolt/Core/BinaryFunction.h +++ b/bolt/include/bolt/Core/BinaryFunction.h @@ -388,6 +388,10 @@ class BinaryFunction { /// The profile data for the number of times the function was executed. uint64_t ExecutionCount{COUNT_NO_PROFILE}; + /// Profile data for the number of times this function was entered from + /// external code (DSO, JIT, etc). + uint64_t ExternEntryCount{0}; + /// Profile match ratio. float ProfileMatchRatio{0.0f}; @@ -1877,6 +1881,10 @@ class BinaryFunction { return *this; } + /// Set the profile data for the number of times the function was entered from + /// external code (DSO/JIT). + void setExternEntryCount(uint64_t Count) { ExternEntryCount = Count; } + /// Adjust execution count for the function by a given \p Count. The value /// \p Count will be subtracted from the current function count. /// @@ -1904,6 +1912,10 @@ class BinaryFunction { /// Return COUNT_NO_PROFILE if there's no profile info. uint64_t getExecutionCount() const { return ExecutionCount; } + /// Return the profile information about the number of times the function was + /// entered from external code (DSO/JIT). + uint64_t getExternEntryCount() const { return ExternEntryCount; } + /// Return the raw profile information about the number of branch /// executions corresponding to this function. uint64_t getRawSampleCount() const { return RawSampleCount; } diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h index b23345298550..804100db8079 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -405,7 +405,7 @@ class MCPlusBuilder { bool equals(const MCExpr &A, const MCExpr &B, CompFuncTy Comp) const; - virtual bool equals(const MCTargetExpr &A, const MCTargetExpr &B, + virtual bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B, CompFuncTy Comp) const; virtual bool isBranch(const MCInst &Inst) const { diff --git a/bolt/include/bolt/Passes/PAuthGadgetScanner.h b/bolt/include/bolt/Passes/PAuthGadgetScanner.h index 98a49df862eb..721fd664a325 100644 --- a/bolt/include/bolt/Passes/PAuthGadgetScanner.h +++ b/bolt/include/bolt/Passes/PAuthGadgetScanner.h @@ -77,9 +77,7 @@ struct MCInstInBFReference { return BF == RHS.BF && Offset == RHS.Offset; } bool operator<(const MCInstInBFReference &RHS) const { - if (BF != RHS.BF) - return BF < RHS.BF; - return Offset < RHS.Offset; + return std::tie(BF, Offset) < std::tie(RHS.BF, RHS.Offset); } operator MCInst &() const { assert(BF != nullptr); @@ -201,8 +199,7 @@ namespace PAuthGadgetScanner { // to distinguish intermediate and final results at the type level. // // Here is an overview of issue life-cycle: -// * an analysis (SrcSafetyAnalysis at now, DstSafetyAnalysis will be added -// later to support the detection of authentication oracles) computes register +// * an analysis (SrcSafetyAnalysis or DstSafetyAnalysis) computes register // state for each instruction in the function. // * for each instruction, it is checked whether it is a gadget of some kind, // taking the computed state into account. If a gadget is found, its kind @@ -275,6 +272,11 @@ class ExtraInfo { virtual ~ExtraInfo() {} }; +/// The set of instructions writing to the affected register in an unsafe +/// manner. +/// +/// This is a hint to be printed alongside the report. It should be further +/// analyzed by the user. class ClobberingInfo : public ExtraInfo { SmallVector ClobberingInstrs; @@ -284,6 +286,20 @@ class ClobberingInfo : public ExtraInfo { void print(raw_ostream &OS, const MCInstReference Location) const override; }; +/// The set of instructions leaking the authenticated pointer before the +/// result of authentication was checked. +/// +/// This is a hint to be printed alongside the report. It should be further +/// analyzed by the user. +class LeakageInfo : public ExtraInfo { + SmallVector LeakingInstrs; + +public: + LeakageInfo(ArrayRef Instrs) : LeakingInstrs(Instrs) {} + + void print(raw_ostream &OS, const MCInstReference Location) const override; +}; + /// A brief version of a report that can be further augmented with the details. /// /// A half-baked report produced on the first run of the analysis. An extra, @@ -324,6 +340,9 @@ class FunctionAnalysisContext { void findUnsafeUses(SmallVector> &Reports); void augmentUnsafeUseReports(ArrayRef> Reports); + void findUnsafeDefs(SmallVector> &Reports); + void augmentUnsafeDefReports(ArrayRef> Reports); + /// Process the reports which do not have to be augmented, and remove them /// from Reports. void handleSimpleReports(SmallVector> &Reports); diff --git a/bolt/include/bolt/Profile/DataAggregator.h b/bolt/include/bolt/Profile/DataAggregator.h index cb8e81b829a0..cc28a06c151e 100644 --- a/bolt/include/bolt/Profile/DataAggregator.h +++ b/bolt/include/bolt/Profile/DataAggregator.h @@ -78,6 +78,15 @@ class DataAggregator : public DataReader { static bool checkPerfDataMagic(StringRef FileName); private: + struct LBREntry { + uint64_t From; + uint64_t To; + bool Mispred; + }; + friend raw_ostream &operator<<(raw_ostream &OS, const LBREntry &); + + friend struct PerfSpeEventsTestHelper; + struct PerfBranchSample { SmallVector LBR; }; @@ -92,24 +101,29 @@ class DataAggregator : public DataReader { uint64_t Addr; }; + /// Container for the unit of branch data, matching pre-aggregated trace type. + /// Backwards compatible with branch and fall-through types: + /// - if \p To is < 0, the trace only contains branch data (BR_ONLY), + /// - if \p Branch is < 0, the trace only contains fall-through data + /// (FT_ONLY, FT_EXTERNAL_ORIGIN, or FT_EXTERNAL_RETURN). struct Trace { + static constexpr const uint64_t EXTERNAL = 0ULL; + static constexpr const uint64_t BR_ONLY = -1ULL; + static constexpr const uint64_t FT_ONLY = -1ULL; + static constexpr const uint64_t FT_EXTERNAL_ORIGIN = -2ULL; + static constexpr const uint64_t FT_EXTERNAL_RETURN = -3ULL; + + uint64_t Branch; uint64_t From; uint64_t To; - Trace(uint64_t From, uint64_t To) : From(From), To(To) {} - bool operator==(const Trace &Other) const { - return From == Other.From && To == Other.To; - } + auto tie() const { return std::tie(Branch, From, To); } + bool operator==(const Trace &Other) const { return tie() == Other.tie(); } + bool operator<(const Trace &Other) const { return tie() < Other.tie(); } }; + friend raw_ostream &operator<<(raw_ostream &OS, const Trace &); struct TraceHash { - size_t operator()(const Trace &L) const { - return std::hash()(L.From << 32 | L.To); - } - }; - - struct FTInfo { - uint64_t InternCount{0}; - uint64_t ExternCount{0}; + size_t operator()(const Trace &L) const { return hash_combine(L.tie()); } }; struct TakenBranchInfo { @@ -119,8 +133,11 @@ class DataAggregator : public DataReader { /// Intermediate storage for profile data. We save the results of parsing /// and use them later for processing and assigning profile. - std::unordered_map BranchLBRs; - std::unordered_map FallthroughLBRs; + std::unordered_map TraceMap; + std::vector> Traces; + /// Pre-populated addresses of returns, coming from pre-aggregated data or + /// disassembly. Used to disambiguate call-continuation fall-throughs. + std::unordered_set Returns; std::unordered_map BasicSamples; std::vector MemSamples; @@ -193,8 +210,8 @@ class DataAggregator : public DataReader { /// Return a vector of offsets corresponding to a trace in a function /// if the trace is valid, std::nullopt otherwise. std::optional, 16>> - getFallthroughsInTrace(BinaryFunction &BF, const LBREntry &First, - const LBREntry &Second, uint64_t Count = 1) const; + getFallthroughsInTrace(BinaryFunction &BF, const Trace &Trace, uint64_t Count, + bool IsReturn) const; /// Record external entry into the function \p BF. /// @@ -254,12 +271,14 @@ class DataAggregator : public DataReader { uint64_t From, uint64_t To, uint64_t Count, uint64_t Mispreds); + /// Checks if \p Addr corresponds to a return instruction. + bool checkReturn(uint64_t Addr); + /// Register a \p Branch. bool doBranch(uint64_t From, uint64_t To, uint64_t Count, uint64_t Mispreds); /// Register a trace between two LBR entries supplied in execution order. - bool doTrace(const LBREntry &First, const LBREntry &Second, - uint64_t Count = 1); + bool doTrace(const Trace &Trace, uint64_t Count, bool IsReturn); /// Parser helpers /// Return false if we exhausted our parser buffer and finished parsing @@ -372,9 +391,9 @@ class DataAggregator : public DataReader { /// File format syntax: /// E /// S - /// T + /// [TR] /// B - /// [Ff] + /// [Ffr] /// /// where , , have the format [:] /// @@ -385,8 +404,11 @@ class DataAggregator : public DataReader { /// f - an aggregated fall-through with external origin - used to disambiguate /// between a return hitting a basic block head and a regular internal /// jump to the block + /// r - an aggregated fall-through originating at an external return, no + /// checks are performed for a fallthrough start /// T - an aggregated trace: branch from to with a fall-through /// to + /// R - an aggregated trace originating at a return /// /// - build id of the object containing the address. We can skip it for /// the main binary and use "X" for an unknown object. This will save some @@ -476,7 +498,6 @@ class DataAggregator : public DataReader { /// Debugging dump methods void dump() const; - void dump(const LBREntry &LBR) const; void dump(const PerfBranchSample &Sample) const; void dump(const PerfMemSample &Sample) const; @@ -504,6 +525,32 @@ class DataAggregator : public DataReader { friend class YAMLProfileWriter; }; + +inline raw_ostream &operator<<(raw_ostream &OS, + const DataAggregator::LBREntry &L) { + OS << formatv("{0:x} -> {1:x}/{2}", L.From, L.To, L.Mispred ? 'M' : 'P'); + return OS; +} + +inline raw_ostream &operator<<(raw_ostream &OS, + const DataAggregator::Trace &T) { + switch (T.Branch) { + case DataAggregator::Trace::FT_ONLY: + break; + case DataAggregator::Trace::FT_EXTERNAL_ORIGIN: + OS << "X:0 -> "; + break; + case DataAggregator::Trace::FT_EXTERNAL_RETURN: + OS << "X:R -> "; + break; + default: + OS << Twine::utohexstr(T.Branch) << " -> "; + } + OS << Twine::utohexstr(T.From); + if (T.To != DataAggregator::Trace::BR_ONLY) + OS << " ... " << Twine::utohexstr(T.To); + return OS; +} } // namespace bolt } // namespace llvm diff --git a/bolt/include/bolt/Profile/DataReader.h b/bolt/include/bolt/Profile/DataReader.h index 5df1b5a8f4a0..6f527ba3931d 100644 --- a/bolt/include/bolt/Profile/DataReader.h +++ b/bolt/include/bolt/Profile/DataReader.h @@ -32,18 +32,6 @@ namespace bolt { class BinaryFunction; -struct LBREntry { - uint64_t From; - uint64_t To; - bool Mispred; -}; - -inline raw_ostream &operator<<(raw_ostream &OS, const LBREntry &LBR) { - OS << "0x" << Twine::utohexstr(LBR.From) << " -> 0x" - << Twine::utohexstr(LBR.To); - return OS; -} - struct Location { bool IsSymbol; StringRef Name; @@ -109,6 +97,9 @@ struct FuncBranchData { /// Total execution count for the function. int64_t ExecutionCount{0}; + /// Total entry count from external code for the function. + uint64_t ExternEntryCount{0}; + /// Indicate if the data was used. bool Used{false}; diff --git a/bolt/include/bolt/Profile/ProfileYAMLMapping.h b/bolt/include/bolt/Profile/ProfileYAMLMapping.h index a8d9a15311d9..41e2bd1651ef 100644 --- a/bolt/include/bolt/Profile/ProfileYAMLMapping.h +++ b/bolt/include/bolt/Profile/ProfileYAMLMapping.h @@ -206,6 +206,7 @@ struct BinaryFunctionProfile { uint32_t Id{0}; llvm::yaml::Hex64 Hash{0}; uint64_t ExecCount{0}; + uint64_t ExternEntryCount{0}; std::vector Blocks; std::vector InlineTree; bool Used{false}; @@ -218,6 +219,7 @@ template <> struct MappingTraits { YamlIO.mapRequired("fid", BFP.Id); YamlIO.mapRequired("hash", BFP.Hash); YamlIO.mapRequired("exec", BFP.ExecCount); + YamlIO.mapOptional("extern", BFP.ExternEntryCount, 0); YamlIO.mapRequired("nblocks", BFP.NumBasicBlocks); YamlIO.mapOptional("blocks", BFP.Blocks, std::vector()); diff --git a/bolt/include/bolt/Utils/CommandLineOpts.h b/bolt/include/bolt/Utils/CommandLineOpts.h index 4acce5a3e832..a75b6bf720ec 100644 --- a/bolt/include/bolt/Utils/CommandLineOpts.h +++ b/bolt/include/bolt/Utils/CommandLineOpts.h @@ -48,6 +48,7 @@ extern llvm::cl::OptionCategory BinaryAnalysisCategory; extern llvm::cl::opt AlignText; extern llvm::cl::opt AlignFunctions; extern llvm::cl::opt AggregateOnly; +extern llvm::cl::opt ArmSPE; extern llvm::cl::opt BucketsPerLine; extern llvm::cl::opt CompactCodeModel; extern llvm::cl::opt DiffOnly; diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp index 6d1969f5c6c3..b998d7160aae 100644 --- a/bolt/lib/Core/BinaryFunction.cpp +++ b/bolt/lib/Core/BinaryFunction.cpp @@ -471,6 +471,8 @@ void BinaryFunction::print(raw_ostream &OS, std::string Annotation) { OS << "\n Sample Count: " << RawSampleCount; OS << "\n Profile Acc : " << format("%.1f%%", ProfileMatchRatio * 100.0f); } + if (ExternEntryCount) + OS << "\n Extern Entry Count: " << ExternEntryCount; if (opts::PrintDynoStats && !getLayout().block_empty()) { OS << '\n'; diff --git a/bolt/lib/Core/HashUtilities.cpp b/bolt/lib/Core/HashUtilities.cpp index 6bdf30460f34..e01768387d6c 100644 --- a/bolt/lib/Core/HashUtilities.cpp +++ b/bolt/lib/Core/HashUtilities.cpp @@ -67,6 +67,7 @@ std::string hashExpr(BinaryContext &BC, const MCExpr &Expr) { .append(hashInteger(BinaryExpr.getOpcode())) .append(hashExpr(BC, *BinaryExpr.getRHS())); } + case MCExpr::Specifier: case MCExpr::Target: return std::string(); } diff --git a/bolt/lib/Core/MCPlusBuilder.cpp b/bolt/lib/Core/MCPlusBuilder.cpp index 7752079b6153..fa8f4d1df308 100644 --- a/bolt/lib/Core/MCPlusBuilder.cpp +++ b/bolt/lib/Core/MCPlusBuilder.cpp @@ -114,17 +114,19 @@ bool MCPlusBuilder::equals(const MCExpr &A, const MCExpr &B, equals(*BinaryA.getRHS(), *BinaryB.getRHS(), Comp); } - case MCExpr::Target: { - const auto &TargetExprA = cast(A); - const auto &TargetExprB = cast(B); + case MCExpr::Specifier: { + const auto &TargetExprA = cast(A); + const auto &TargetExprB = cast(B); return equals(TargetExprA, TargetExprB, Comp); } + case MCExpr::Target: + llvm_unreachable("Not implemented"); } llvm_unreachable("Invalid expression kind!"); } -bool MCPlusBuilder::equals(const MCTargetExpr &A, const MCTargetExpr &B, +bool MCPlusBuilder::equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B, CompFuncTy Comp) const { llvm_unreachable("target-specific expressions are unsupported"); } diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp index 971ea5fdef42..95e831fe9c8c 100644 --- a/bolt/lib/Passes/PAuthGadgetScanner.cpp +++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp @@ -152,6 +152,8 @@ class TrackedRegisters { // in the gadgets to be reported. This information is used in the second run // to also track which instructions last wrote to those registers. +typedef SmallPtrSet SetOfRelatedInsts; + /// A state representing which registers are safe to use by an instruction /// at a given program point. /// @@ -195,7 +197,7 @@ struct SrcState { /// pac-ret analysis, the expectation is that almost all return instructions /// only use register `X30`, and therefore, this vector will probably have /// length 1 in the second run. - std::vector> LastInstWritingReg; + std::vector LastInstWritingReg; /// Construct an empty state. SrcState() {} @@ -230,12 +232,11 @@ struct SrcState { bool operator!=(const SrcState &RHS) const { return !((*this) == RHS); } }; -static void -printLastInsts(raw_ostream &OS, - ArrayRef> LastInstWritingReg) { +static void printInstsShort(raw_ostream &OS, + ArrayRef Insts) { OS << "Insts: "; - for (unsigned I = 0; I < LastInstWritingReg.size(); ++I) { - auto &Set = LastInstWritingReg[I]; + for (unsigned I = 0; I < Insts.size(); ++I) { + auto &Set = Insts[I]; OS << "[" << I << "]("; for (const MCInst *MCInstP : Set) OS << MCInstP << " "; @@ -243,14 +244,14 @@ printLastInsts(raw_ostream &OS, } } -raw_ostream &operator<<(raw_ostream &OS, const SrcState &S) { +static raw_ostream &operator<<(raw_ostream &OS, const SrcState &S) { OS << "src-state<"; if (S.empty()) { OS << "empty"; } else { OS << "SafeToDerefRegs: " << S.SafeToDerefRegs << ", "; OS << "TrustedRegs: " << S.TrustedRegs << ", "; - printLastInsts(OS, S.LastInstWritingReg); + printInstsShort(OS, S.LastInstWritingReg); } OS << ">"; return OS; @@ -279,7 +280,7 @@ void SrcStatePrinter::print(raw_ostream &OS, const SrcState &S) const { OS << ", TrustedRegs: "; RegStatePrinter.print(OS, S.TrustedRegs); OS << ", "; - printLastInsts(OS, S.LastInstWritingReg); + printInstsShort(OS, S.LastInstWritingReg); } OS << ">"; } @@ -323,13 +324,12 @@ class SrcSafetyAnalysis { DenseMap> CheckerSequenceInfo; - SmallPtrSet &lastWritingInsts(SrcState &S, - MCPhysReg Reg) const { + SetOfRelatedInsts &lastWritingInsts(SrcState &S, MCPhysReg Reg) const { unsigned Index = RegsToTrackInstsFor.getIndex(Reg); return S.LastInstWritingReg[Index]; } - const SmallPtrSet &lastWritingInsts(const SrcState &S, - MCPhysReg Reg) const { + const SetOfRelatedInsts &lastWritingInsts(const SrcState &S, + MCPhysReg Reg) const { unsigned Index = RegsToTrackInstsFor.getIndex(Reg); return S.LastInstWritingReg[Index]; } @@ -430,11 +430,13 @@ class SrcSafetyAnalysis { } SrcState computeNext(const MCInst &Point, const SrcState &Cur) { + if (BC.MIB->isCFI(Point)) + return Cur; + SrcStatePrinter P(BC); LLVM_DEBUG({ dbgs() << " SrcSafetyAnalysis::ComputeNext("; - BC.InstPrinter->printInst(&const_cast(Point), 0, "", *BC.STI, - dbgs()); + BC.InstPrinter->printInst(&Point, 0, "", *BC.STI, dbgs()); dbgs() << ", "; P.print(dbgs(), Cur); dbgs() << ")\n"; @@ -612,6 +614,42 @@ class DataflowSrcSafetyAnalysis StringRef getAnnotationName() const { return "DataflowSrcSafetyAnalysis"; } }; +/// A helper base class for implementing a simplified counterpart of a dataflow +/// analysis for functions without CFG information. +template class CFGUnawareAnalysis { + BinaryContext &BC; + BinaryFunction &BF; + MCPlusBuilder::AllocatorIdTy AllocId; + unsigned StateAnnotationIndex; + + void cleanStateAnnotations() { + for (auto &I : BF.instrs()) + BC.MIB->removeAnnotation(I.second, StateAnnotationIndex); + } + +protected: + CFGUnawareAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId, + StringRef AnnotationName) + : BC(BF.getBinaryContext()), BF(BF), AllocId(AllocId) { + StateAnnotationIndex = BC.MIB->getOrCreateAnnotationIndex(AnnotationName); + } + + void setState(MCInst &Inst, const StateTy &S) { + // Check if we need to remove an old annotation (this is the case if + // this is the second, detailed run of the analysis). + if (BC.MIB->hasAnnotation(Inst, StateAnnotationIndex)) + BC.MIB->removeAnnotation(Inst, StateAnnotationIndex); + // Attach the state. + BC.MIB->addAnnotation(Inst, StateAnnotationIndex, S, AllocId); + } + + const StateTy &getState(const MCInst &Inst) const { + return BC.MIB->getAnnotationAs(Inst, StateAnnotationIndex); + } + + virtual ~CFGUnawareAnalysis() { cleanStateAnnotations(); } +}; + // A simplified implementation of DataflowSrcSafetyAnalysis for functions // lacking CFG information. // @@ -646,15 +684,10 @@ class DataflowSrcSafetyAnalysis // of instructions without labels in between. These sequences can be processed // the same way basic blocks are processed by data-flow analysis, assuming // pessimistically that all registers are unsafe at the start of each sequence. -class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis { +class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis, + public CFGUnawareAnalysis { + using SrcSafetyAnalysis::BC; BinaryFunction &BF; - MCPlusBuilder::AllocatorIdTy AllocId; - unsigned StateAnnotationIndex; - - void cleanStateAnnotations() { - for (auto &I : BF.instrs()) - BC.MIB->removeAnnotation(I.second, StateAnnotationIndex); - } /// Creates a state with all registers marked unsafe (not to be confused /// with empty state). @@ -666,15 +699,16 @@ class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis { CFGUnawareSrcSafetyAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId, ArrayRef RegsToTrackInstsFor) - : SrcSafetyAnalysis(BF, RegsToTrackInstsFor), BF(BF), AllocId(AllocId) { - StateAnnotationIndex = - BC.MIB->getOrCreateAnnotationIndex("CFGUnawareSrcSafetyAnalysis"); + : SrcSafetyAnalysis(BF, RegsToTrackInstsFor), + CFGUnawareAnalysis(BF, AllocId, "CFGUnawareSrcSafetyAnalysis"), BF(BF) { } void run() override { SrcState S = createEntryState(); for (auto &I : BF.instrs()) { MCInst &Inst = I.second; + if (BC.MIB->isCFI(Inst)) + continue; // If there is a label before this instruction, it is possible that it // can be jumped-to, thus conservatively resetting S. As an exception, @@ -687,12 +721,8 @@ class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis { S = createUnsafeState(); } - // Check if we need to remove an old annotation (this is the case if - // this is the second, detailed, run of the analysis). - if (BC.MIB->hasAnnotation(Inst, StateAnnotationIndex)) - BC.MIB->removeAnnotation(Inst, StateAnnotationIndex); // Attach the state *before* this instruction executes. - BC.MIB->addAnnotation(Inst, StateAnnotationIndex, S, AllocId); + setState(Inst, S); // Compute the state after this instruction executes. S = computeNext(Inst, S); @@ -700,10 +730,8 @@ class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis { } const SrcState &getStateBefore(const MCInst &Inst) const override { - return BC.MIB->getAnnotationAs(Inst, StateAnnotationIndex); + return getState(Inst); } - - ~CFGUnawareSrcSafetyAnalysis() { cleanStateAnnotations(); } }; std::shared_ptr @@ -717,6 +745,483 @@ SrcSafetyAnalysis::create(BinaryFunction &BF, RegsToTrackInstsFor); } +/// A state representing which registers are safe to be used as the destination +/// operand of an authentication instruction. +/// +/// Similar to SrcState, it is the responsibility of the analysis to take +/// register aliasing into account. +/// +/// Depending on the implementation (such as whether FEAT_FPAC is implemented +/// by an AArch64 CPU or not), it may be possible that an authentication +/// instruction returns an invalid pointer on failure instead of terminating +/// the program immediately (assuming the program will crash as soon as that +/// pointer is dereferenced). Since few bits are usually allocated for the PAC +/// field (such as less than 16 bits on a typical AArch64 system), an attacker +/// can try every possible signature and guess the correct one if there is a +/// gadget that tells whether the particular pointer has a correct signature +/// (a so called "authentication oracle"). For that reason, it should be +/// impossible for an attacker to test if a pointer is correctly signed - +/// either the program should be terminated on authentication failure or +/// the result of authentication should not be accessible to an attacker. +/// +/// Considering the instructions in forward order as they are executed, a +/// restricted set of operations can be allowed on any register containing a +/// value derived from the result of an authentication instruction until that +/// value is checked not to contain the result of a failed authentication. +/// In DstSafetyAnalysis, these rules are adapted, so that the safety property +/// for a register is computed by iterating the instructions in backward order. +/// Then the resulting properties are used at authentication instruction sites +/// to check output registers and report the particular instruction if it writes +/// to an unsafe register. +/// +/// Another approach would be to simulate the above rules as-is, iterating over +/// the instructions in forward direction. To make it possible to report the +/// particular instructions as oracles, this would probably require tracking +/// references to these instructions for each register currently containing +/// sensitive data. +/// +/// In DstSafetyAnalysis, the source register Xn of an instruction Inst is safe +/// if at least one of the following is true: +/// * Inst checks if Xn contains the result of a successful authentication and +/// terminates the program on failure. Note that Inst can either naturally +/// dereference Xn (load, branch, return, etc. instructions) or be the first +/// instruction of an explicit checking sequence. +/// * Inst performs safe address arithmetic AND both source and result +/// registers, as well as any temporary registers, must be safe after +/// execution of Inst (temporaries are not used on AArch64 and thus not +/// currently supported/allowed). +/// See MCPlusBuilder::analyzeAddressArithmeticsForPtrAuth for the details. +/// * Inst fully overwrites Xn with a constant. +struct DstState { + /// The set of registers whose values cannot be inspected by an attacker in + /// a way usable as an authentication oracle. The results of authentication + /// instructions should only be written to such registers. + BitVector CannotEscapeUnchecked; + + /// A vector of sets, only used on the second analysis run. + /// Each element in this vector represents one of the tracked registers. + /// For each such register we track the set of first instructions that leak + /// the authenticated pointer before it was checked. This is intended to + /// provide clues on which instruction made the particular register unsafe. + /// + /// Please note that the mapping from MCPhysReg values to indexes in this + /// vector is provided by RegsToTrackInstsFor field of DstSafetyAnalysis. + std::vector FirstInstLeakingReg; + + /// Constructs an empty state. + DstState() {} + + DstState(unsigned NumRegs, unsigned NumRegsToTrack) + : CannotEscapeUnchecked(NumRegs), FirstInstLeakingReg(NumRegsToTrack) {} + + DstState &merge(const DstState &StateIn) { + if (StateIn.empty()) + return *this; + if (empty()) + return (*this = StateIn); + + CannotEscapeUnchecked &= StateIn.CannotEscapeUnchecked; + for (unsigned I = 0; I < FirstInstLeakingReg.size(); ++I) + for (const MCInst *J : StateIn.FirstInstLeakingReg[I]) + FirstInstLeakingReg[I].insert(J); + return *this; + } + + /// Returns true if this object does not store state of any registers - + /// neither safe, nor unsafe ones. + bool empty() const { return CannotEscapeUnchecked.empty(); } + + bool operator==(const DstState &RHS) const { + return CannotEscapeUnchecked == RHS.CannotEscapeUnchecked && + FirstInstLeakingReg == RHS.FirstInstLeakingReg; + } + bool operator!=(const DstState &RHS) const { return !((*this) == RHS); } +}; + +static raw_ostream &operator<<(raw_ostream &OS, const DstState &S) { + OS << "dst-state<"; + if (S.empty()) { + OS << "empty"; + } else { + OS << "CannotEscapeUnchecked: " << S.CannotEscapeUnchecked << ", "; + printInstsShort(OS, S.FirstInstLeakingReg); + } + OS << ">"; + return OS; +} + +class DstStatePrinter { +public: + void print(raw_ostream &OS, const DstState &S) const; + explicit DstStatePrinter(const BinaryContext &BC) : BC(BC) {} + +private: + const BinaryContext &BC; +}; + +void DstStatePrinter::print(raw_ostream &OS, const DstState &S) const { + RegStatePrinter RegStatePrinter(BC); + OS << "dst-state<"; + if (S.empty()) { + assert(S.CannotEscapeUnchecked.empty()); + assert(S.FirstInstLeakingReg.empty()); + OS << "empty"; + } else { + OS << "CannotEscapeUnchecked: "; + RegStatePrinter.print(OS, S.CannotEscapeUnchecked); + OS << ", "; + printInstsShort(OS, S.FirstInstLeakingReg); + } + OS << ">"; +} + +/// Computes which registers are safe to be written to by auth instructions. +/// +/// This is the base class for two implementations: a dataflow-based analysis +/// which is intended to be used for most functions and a simplified CFG-unaware +/// version for functions without reconstructed CFG. +class DstSafetyAnalysis { +public: + DstSafetyAnalysis(BinaryFunction &BF, ArrayRef RegsToTrackInstsFor) + : BC(BF.getBinaryContext()), NumRegs(BC.MRI->getNumRegs()), + RegsToTrackInstsFor(RegsToTrackInstsFor) {} + + virtual ~DstSafetyAnalysis() {} + + static std::shared_ptr + create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId, + ArrayRef RegsToTrackInstsFor); + + virtual void run() = 0; + virtual const DstState &getStateAfter(const MCInst &Inst) const = 0; + +protected: + BinaryContext &BC; + const unsigned NumRegs; + + const TrackedRegisters RegsToTrackInstsFor; + + /// Stores information about the detected instruction sequences emitted to + /// check an authenticated pointer. Specifically, if such sequence is detected + /// in a basic block, it maps the first instruction of that sequence to the + /// register being checked. + /// + /// As the detection of such sequences requires iterating over the adjacent + /// instructions, it should be done before calling computeNext(), which + /// operates on separate instructions. + DenseMap RegCheckedAt; + + SetOfRelatedInsts &firstLeakingInsts(DstState &S, MCPhysReg Reg) const { + unsigned Index = RegsToTrackInstsFor.getIndex(Reg); + return S.FirstInstLeakingReg[Index]; + } + const SetOfRelatedInsts &firstLeakingInsts(const DstState &S, + MCPhysReg Reg) const { + unsigned Index = RegsToTrackInstsFor.getIndex(Reg); + return S.FirstInstLeakingReg[Index]; + } + + /// Creates a state with all registers marked unsafe (not to be confused + /// with empty state). + DstState createUnsafeState() { + return DstState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters()); + } + + /// Returns the set of registers that can be leaked by this instruction. + /// A register is considered leaked if it has any intersection with any + /// register read by Inst. This is similar to how the set of clobbered + /// registers is computed, but taking input operands instead of outputs. + BitVector getLeakedRegs(const MCInst &Inst) const { + BitVector Leaked(NumRegs); + + // Assume a call can read all registers. + if (BC.MIB->isCall(Inst)) { + Leaked.set(); + return Leaked; + } + + // Compute the set of registers overlapping with any register used by + // this instruction. + + const MCInstrDesc &Desc = BC.MII->get(Inst.getOpcode()); + + for (MCPhysReg Reg : Desc.implicit_uses()) + Leaked |= BC.MIB->getAliases(Reg, /*OnlySmaller=*/false); + + for (const MCOperand &Op : BC.MIB->useOperands(Inst)) { + if (Op.isReg()) + Leaked |= BC.MIB->getAliases(Op.getReg(), /*OnlySmaller=*/false); + } + + return Leaked; + } + + SmallVector getRegsMadeProtected(const MCInst &Inst, + const BitVector &LeakedRegs, + const DstState &Cur) const { + SmallVector Regs; + + // A pointer can be checked, or + if (auto CheckedReg = + BC.MIB->getAuthCheckedReg(Inst, /*MayOverwrite=*/true)) + Regs.push_back(*CheckedReg); + if (RegCheckedAt.contains(&Inst)) + Regs.push_back(RegCheckedAt.at(&Inst)); + + // ... it can be used as a branch target, or + if (BC.MIB->isIndirectBranch(Inst) || BC.MIB->isIndirectCall(Inst)) { + bool IsAuthenticated; + MCPhysReg BranchDestReg = + BC.MIB->getRegUsedAsIndirectBranchDest(Inst, IsAuthenticated); + assert(BranchDestReg != BC.MIB->getNoRegister()); + if (!IsAuthenticated) + Regs.push_back(BranchDestReg); + } + + // ... it can be used as a return target, or + if (BC.MIB->isReturn(Inst)) { + bool IsAuthenticated = false; + std::optional RetReg = + BC.MIB->getRegUsedAsRetDest(Inst, IsAuthenticated); + if (RetReg && !IsAuthenticated) + Regs.push_back(*RetReg); + } + + // ... an address can be updated in a safe manner, or + if (auto DstAndSrc = BC.MIB->analyzeAddressArithmeticsForPtrAuth(Inst)) { + MCPhysReg DstReg, SrcReg; + std::tie(DstReg, SrcReg) = *DstAndSrc; + // Note that *all* registers containing the derived values must be safe, + // both source and destination ones. No temporaries are supported at now. + if (Cur.CannotEscapeUnchecked[SrcReg] && + Cur.CannotEscapeUnchecked[DstReg]) + Regs.push_back(SrcReg); + } + + // ... the register can be overwritten in whole with a constant: for that + // purpose, look for the instructions with no register inputs (neither + // explicit nor implicit ones) and no side effects (to rule out reading + // not modelled locations). + const MCInstrDesc &Desc = BC.MII->get(Inst.getOpcode()); + bool HasExplicitSrcRegs = llvm::any_of(BC.MIB->useOperands(Inst), + [](auto Op) { return Op.isReg(); }); + if (!Desc.hasUnmodeledSideEffects() && !HasExplicitSrcRegs && + Desc.implicit_uses().empty()) { + for (const MCOperand &Def : BC.MIB->defOperands(Inst)) + Regs.push_back(Def.getReg()); + } + + return Regs; + } + + DstState computeNext(const MCInst &Point, const DstState &Cur) { + if (BC.MIB->isCFI(Point)) + return Cur; + + DstStatePrinter P(BC); + LLVM_DEBUG({ + dbgs() << " DstSafetyAnalysis::ComputeNext("; + BC.InstPrinter->printInst(&Point, 0, "", *BC.STI, dbgs()); + dbgs() << ", "; + P.print(dbgs(), Cur); + dbgs() << ")\n"; + }); + + // 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()) { + LLVM_DEBUG( + { dbgs() << "Skipping computeNext(Point, Cur) as Cur is empty.\n"; }); + return DstState(); + } + + // First, compute various properties of the instruction, taking the state + // after its execution into account, if necessary. + + BitVector LeakedRegs = getLeakedRegs(Point); + SmallVector NewProtectedRegs = + getRegsMadeProtected(Point, LeakedRegs, Cur); + + // Then, compute the state before this instruction is executed. + DstState Next = Cur; + + Next.CannotEscapeUnchecked.reset(LeakedRegs); + for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters()) { + if (LeakedRegs[Reg]) + firstLeakingInsts(Next, Reg) = {&Point}; + } + + BitVector NewProtectedSubregs(NumRegs); + for (MCPhysReg Reg : NewProtectedRegs) + NewProtectedSubregs |= BC.MIB->getAliases(Reg, /*OnlySmaller=*/true); + Next.CannotEscapeUnchecked |= NewProtectedSubregs; + for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters()) { + if (NewProtectedSubregs[Reg]) + firstLeakingInsts(Next, Reg).clear(); + } + + LLVM_DEBUG({ + dbgs() << " .. result: ("; + P.print(dbgs(), Next); + dbgs() << ")\n"; + }); + + return Next; + } + +public: + std::vector getLeakingInsts(const MCInst &Inst, + BinaryFunction &BF, + MCPhysReg LeakedReg) const { + const DstState &S = getStateAfter(Inst); + + std::vector Result; + for (const MCInst *Inst : firstLeakingInsts(S, LeakedReg)) { + MCInstReference Ref = MCInstReference::get(Inst, BF); + assert(Ref && "Expected Inst to be found"); + Result.push_back(Ref); + } + return Result; + } +}; + +class DataflowDstSafetyAnalysis + : public DstSafetyAnalysis, + public DataflowAnalysis { + using DFParent = DataflowAnalysis; + friend DFParent; + + using DstSafetyAnalysis::BC; + using DstSafetyAnalysis::computeNext; + +public: + DataflowDstSafetyAnalysis(BinaryFunction &BF, + MCPlusBuilder::AllocatorIdTy AllocId, + ArrayRef RegsToTrackInstsFor) + : DstSafetyAnalysis(BF, RegsToTrackInstsFor), DFParent(BF, AllocId) {} + + const DstState &getStateAfter(const MCInst &Inst) const override { + // The dataflow analysis base class iterates backwards over the + // instructions, thus "after" vs. "before" difference. + return DFParent::getStateBefore(Inst).get(); + } + + void run() override { + for (BinaryBasicBlock &BB : Func) { + if (auto CheckerInfo = BC.MIB->getAuthCheckedReg(BB)) { + LLVM_DEBUG({ + dbgs() << "Found pointer checking sequence in " << BB.getName() + << ":\n"; + traceReg(BC, "Checked register", CheckerInfo->first); + traceInst(BC, "First instruction", *CheckerInfo->second); + }); + RegCheckedAt[CheckerInfo->second] = CheckerInfo->first; + } + } + DFParent::run(); + } + +protected: + void preflight() {} + + DstState getStartingStateAtBB(const BinaryBasicBlock &BB) { + // In general, the initial state should be empty, not everything-is-unsafe, + // to give a chance for some meaningful state to be propagated to BB from + // an indirectly reachable "exit basic block" ending with a return or tail + // call instruction. + // + // 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). + if (BB.succ_empty()) + return createUnsafeState(); + + return DstState(); + } + + DstState getStartingStateAtPoint(const MCInst &Point) { return DstState(); } + + void doConfluence(DstState &StateOut, const DstState &StateIn) { + DstStatePrinter P(BC); + LLVM_DEBUG({ + dbgs() << " DataflowDstSafetyAnalysis::Confluence(\n"; + dbgs() << " State 1: "; + P.print(dbgs(), StateOut); + dbgs() << "\n"; + dbgs() << " State 2: "; + P.print(dbgs(), StateIn); + dbgs() << ")\n"; + }); + + StateOut.merge(StateIn); + + LLVM_DEBUG({ + dbgs() << " merged state: "; + P.print(dbgs(), StateOut); + dbgs() << "\n"; + }); + } + + StringRef getAnnotationName() const { return "DataflowDstSafetyAnalysis"; } +}; + +class CFGUnawareDstSafetyAnalysis : public DstSafetyAnalysis, + public CFGUnawareAnalysis { + using DstSafetyAnalysis::BC; + BinaryFunction &BF; + +public: + CFGUnawareDstSafetyAnalysis(BinaryFunction &BF, + MCPlusBuilder::AllocatorIdTy AllocId, + ArrayRef RegsToTrackInstsFor) + : DstSafetyAnalysis(BF, RegsToTrackInstsFor), + CFGUnawareAnalysis(BF, AllocId, "CFGUnawareDstSafetyAnalysis"), BF(BF) { + } + + void run() override { + DstState S = createUnsafeState(); + for (auto &I : llvm::reverse(BF.instrs())) { + MCInst &Inst = I.second; + if (BC.MIB->isCFI(Inst)) + continue; + + // If Inst can change the control flow, we cannot be sure that the next + // instruction (to be executed in analyzed program) is the one processed + // on the previous iteration, thus pessimistically reset S before + // starting to analyze Inst. + if (BC.MIB->isCall(Inst) || BC.MIB->isBranch(Inst) || + BC.MIB->isReturn(Inst)) { + LLVM_DEBUG({ traceInst(BC, "Control flow instruction", Inst); }); + S = createUnsafeState(); + } + + // Attach the state *after* this instruction executes. + setState(Inst, S); + + // Compute the next state. + S = computeNext(Inst, S); + } + } + + const DstState &getStateAfter(const MCInst &Inst) const override { + return getState(Inst); + } +}; + +std::shared_ptr +DstSafetyAnalysis::create(BinaryFunction &BF, + MCPlusBuilder::AllocatorIdTy AllocId, + ArrayRef RegsToTrackInstsFor) { + if (BF.hasCFG()) + return std::make_shared(BF, AllocId, + RegsToTrackInstsFor); + return std::make_shared(BF, AllocId, + RegsToTrackInstsFor); +} + // This function could return PartialReport, but currently T is always // MCPhysReg, even though it is an implementation detail. static PartialReport make_generic_report(MCInstReference Location, @@ -808,6 +1313,37 @@ shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst, return make_gadget_report(SigningOracleKind, Inst, *SignedReg); } +static std::optional> +shouldReportAuthOracle(const BinaryContext &BC, const MCInstReference &Inst, + const DstState &S) { + static const GadgetKind AuthOracleKind("authentication oracle found"); + + bool IsChecked = false; + std::optional AuthReg = + BC.MIB->getWrittenAuthenticatedReg(Inst, IsChecked); + if (!AuthReg || IsChecked) + return std::nullopt; + + LLVM_DEBUG({ + traceInst(BC, "Found auth inst", Inst); + traceReg(BC, "Authenticated reg", *AuthReg); + }); + + if (S.empty()) { + LLVM_DEBUG({ dbgs() << " DstState is empty!\n"; }); + return make_generic_report( + Inst, "Warning: no state computed for an authentication instruction " + "(possibly unreachable)"); + } + + LLVM_DEBUG( + { traceRegMask(BC, "safe output registers", S.CannotEscapeUnchecked); }); + if (S.CannotEscapeUnchecked[*AuthReg]) + return std::nullopt; + + return make_gadget_report(AuthOracleKind, Inst, *AuthReg); +} + template static void iterateOverInstrs(BinaryFunction &BF, T Fn) { if (BF.hasCFG()) { for (BinaryBasicBlock &BB : BF) @@ -840,6 +1376,9 @@ void FunctionAnalysisContext::findUnsafeUses( }); iterateOverInstrs(BF, [&](MCInstReference Inst) { + if (BC.MIB->isCFI(Inst)) + return; + const SrcState &S = Analysis->getStateBefore(Inst); // If non-empty state was never propagated from the entry basic block @@ -889,6 +1428,55 @@ void FunctionAnalysisContext::augmentUnsafeUseReports( } } +void FunctionAnalysisContext::findUnsafeDefs( + SmallVector> &Reports) { + if (PacRetGadgetsOnly) + return; + + auto Analysis = DstSafetyAnalysis::create(BF, AllocatorId, {}); + LLVM_DEBUG({ dbgs() << "Running dst register safety analysis...\n"; }); + Analysis->run(); + LLVM_DEBUG({ + dbgs() << "After dst register safety analysis:\n"; + BF.dump(); + }); + + iterateOverInstrs(BF, [&](MCInstReference Inst) { + if (BC.MIB->isCFI(Inst)) + return; + + const DstState &S = Analysis->getStateAfter(Inst); + + if (auto Report = shouldReportAuthOracle(BC, Inst, S)) + Reports.push_back(*Report); + }); +} + +void FunctionAnalysisContext::augmentUnsafeDefReports( + ArrayRef> Reports) { + SmallVector RegsToTrack = collectRegsToTrack(Reports); + // Re-compute the analysis with register tracking. + auto Analysis = DstSafetyAnalysis::create(BF, AllocatorId, RegsToTrack); + LLVM_DEBUG( + { dbgs() << "\nRunning detailed dst register safety analysis...\n"; }); + Analysis->run(); + LLVM_DEBUG({ + dbgs() << "After detailed dst register safety analysis:\n"; + BF.dump(); + }); + + // Augment gadget reports. + for (auto &Report : Reports) { + MCInstReference Location = Report.Issue->Location; + LLVM_DEBUG({ traceInst(BC, "Attaching leakage info to", Location); }); + assert(Report.RequestedDetails && + "Should be removed by handleSimpleReports"); + auto DetailedInfo = std::make_shared( + Analysis->getLeakingInsts(Location, BF, *Report.RequestedDetails)); + Result.Diagnostics.emplace_back(Report.Issue, DetailedInfo); + } +} + void FunctionAnalysisContext::handleSimpleReports( SmallVector> &Reports) { // Before re-running the detailed analysis, process the reports which do not @@ -912,6 +1500,12 @@ void FunctionAnalysisContext::run() { handleSimpleReports(UnsafeUses); if (!UnsafeUses.empty()) augmentUnsafeUseReports(UnsafeUses); + + SmallVector> UnsafeDefs; + findUnsafeDefs(UnsafeDefs); + handleSimpleReports(UnsafeDefs); + if (!UnsafeDefs.empty()) + augmentUnsafeDefReports(UnsafeDefs); } void Analysis::runOnFunction(BinaryFunction &BF, @@ -1015,6 +1609,12 @@ void ClobberingInfo::print(raw_ostream &OS, printRelatedInstrs(OS, Location, ClobberingInstrs); } +void LeakageInfo::print(raw_ostream &OS, const MCInstReference Location) const { + OS << " The " << LeakingInstrs.size() + << " instructions that leak the affected registers are:\n"; + printRelatedInstrs(OS, Location, LeakingInstrs); +} + void GenericDiagnostic::generateReport(raw_ostream &OS, const BinaryContext &BC) const { printBasicInfo(OS, BC, Text); diff --git a/bolt/lib/Passes/ProfileQualityStats.cpp b/bolt/lib/Passes/ProfileQualityStats.cpp index dfd74d3dd571..64cc662c3ab2 100644 --- a/bolt/lib/Passes/ProfileQualityStats.cpp +++ b/bolt/lib/Passes/ProfileQualityStats.cpp @@ -532,6 +532,9 @@ void computeFlowMappings(const BinaryContext &BC, FlowInfo &TotalFlowMap) { std::vector &MaxCountMap = TotalMaxCountMaps[FunctionNum]; std::vector &MinCountMap = TotalMinCountMaps[FunctionNum]; + // Record external entry count into CallGraphIncomingFlows + CallGraphIncomingFlows[FunctionNum] += Function->getExternEntryCount(); + // Update MaxCountMap, MinCountMap, and CallGraphIncomingFlows auto recordCall = [&](const BinaryBasicBlock *SourceBB, const MCSymbol *DestSymbol, uint64_t Count, diff --git a/bolt/lib/Passes/RetpolineInsertion.cpp b/bolt/lib/Passes/RetpolineInsertion.cpp index 98e5a8fba645..bda26206e16c 100644 --- a/bolt/lib/Passes/RetpolineInsertion.cpp +++ b/bolt/lib/Passes/RetpolineInsertion.cpp @@ -195,7 +195,7 @@ std::string createRetpolineFunctionTag(BinaryContext &BC, TagOS << "+"; if (MemRef.DispExpr) - MemRef.DispExpr->print(TagOS, BC.AsmInfo.get()); + BC.AsmInfo->printExpr(TagOS, *MemRef.DispExpr); else TagOS << MemRef.DispImm; diff --git a/bolt/lib/Profile/BoltAddressTranslation.cpp b/bolt/lib/Profile/BoltAddressTranslation.cpp index a253522e4fb1..7ad4e6a2e141 100644 --- a/bolt/lib/Profile/BoltAddressTranslation.cpp +++ b/bolt/lib/Profile/BoltAddressTranslation.cpp @@ -546,7 +546,7 @@ BoltAddressTranslation::getFallthroughsInTrace(uint64_t FuncAddress, return Res; for (auto Iter = FromIter; Iter != ToIter;) { - const uint32_t Src = Iter->first; + const uint32_t Src = Iter->second >> 1; if (Iter->second & BRANCHENTRY) { ++Iter; continue; @@ -557,7 +557,7 @@ BoltAddressTranslation::getFallthroughsInTrace(uint64_t FuncAddress, ++Iter; if (Iter->second & BRANCHENTRY) break; - Res.emplace_back(Src, Iter->first); + Res.emplace_back(Src, Iter->second >> 1); } return Res; diff --git a/bolt/lib/Profile/DataAggregator.cpp b/bolt/lib/Profile/DataAggregator.cpp index f05c40ebcd2f..5c8af3710720 100644 --- a/bolt/lib/Profile/DataAggregator.cpp +++ b/bolt/lib/Profile/DataAggregator.cpp @@ -49,6 +49,9 @@ static cl::opt cl::desc("aggregate basic samples (without LBR info)"), cl::cat(AggregatorCategory)); +cl::opt ArmSPE("spe", cl::desc("Enable Arm SPE mode."), + cl::cat(AggregatorCategory)); + static cl::opt ITraceAggregation("itrace", cl::desc("Generate LBR info with perf itrace argument"), @@ -61,6 +64,12 @@ FilterMemProfile("filter-mem-profile", cl::init(true), cl::cat(AggregatorCategory)); +static cl::opt ParseMemProfile( + "parse-mem-profile", + cl::desc("enable memory profile parsing if it's present in the input data, " + "on by default unless `--itrace` is set."), + cl::init(true), cl::cat(AggregatorCategory)); + static cl::opt FilterPID("pid", cl::desc("only use samples from process with specified PID"), @@ -175,12 +184,26 @@ void DataAggregator::start() { findPerfExecutable(); + if (opts::ArmSPE) { + // pid from_ip to_ip flags + // where flags could be: + // P/M: whether branch was Predicted or Mispredicted. + // N: optionally appears when the branch was Not-Taken (ie fall-through) + // 12345 0x123/0x456/PN/-/-/8/RET/- + opts::ITraceAggregation = "bl"; + opts::ParseMemProfile = true; + opts::BasicAggregation = false; + } + if (opts::BasicAggregation) { - launchPerfProcess("events without LBR", - MainEventsPPI, + launchPerfProcess("events without LBR", MainEventsPPI, "script -F pid,event,ip", - /*Wait = */false); + /*Wait = */ false); } else if (!opts::ITraceAggregation.empty()) { + // Disable parsing memory profile from trace data, unless requested by user. + if (!opts::ParseMemProfile.getNumOccurrences()) + opts::ParseMemProfile = false; + std::string ItracePerfScriptArgs = llvm::formatv( "script -F pid,brstack --itrace={0}", opts::ITraceAggregation); launchPerfProcess("branch events with itrace", MainEventsPPI, @@ -191,12 +214,9 @@ void DataAggregator::start() { /*Wait = */ false); } - // Note: we launch script for mem events regardless of the option, as the - // command fails fairly fast if mem events were not collected. - launchPerfProcess("mem events", - MemEventsPPI, - "script -F pid,event,addr,ip", - /*Wait = */false); + if (opts::ParseMemProfile) + launchPerfProcess("mem events", MemEventsPPI, "script -F pid,event,addr,ip", + /*Wait = */ false); launchPerfProcess("process events", MMapEventsPPI, "script --show-mmap-events --no-itrace", @@ -217,7 +237,8 @@ void DataAggregator::abort() { sys::Wait(TaskEventsPPI.PI, 1, &Error); sys::Wait(MMapEventsPPI.PI, 1, &Error); sys::Wait(MainEventsPPI.PI, 1, &Error); - sys::Wait(MemEventsPPI.PI, 1, &Error); + if (opts::ParseMemProfile) + sys::Wait(MemEventsPPI.PI, 1, &Error); deleteTempFiles(); @@ -506,7 +527,8 @@ Error DataAggregator::preprocessProfile(BinaryContext &BC) { errs() << "PERF2BOLT: failed to parse samples\n"; // Special handling for memory events - if (!prepareToParse("mem events", MemEventsPPI, MemEventsErrorCallback)) + if (opts::ParseMemProfile && + !prepareToParse("mem events", MemEventsPPI, MemEventsErrorCallback)) if (const std::error_code EC = parseMemEvents()) errs() << "PERF2BOLT: failed to parse memory events: " << EC.message() << '\n'; @@ -514,6 +536,9 @@ Error DataAggregator::preprocessProfile(BinaryContext &BC) { deleteTempFiles(); heatmap: + // Sort parsed traces for faster processing. + llvm::sort(Traces, llvm::less_first()); + if (!opts::HeatmapMode) return Error::success(); @@ -580,15 +605,16 @@ void DataAggregator::processProfile(BinaryContext &BC) { } } - for (auto &FuncBranches : NamesToBranches) + for (auto &FuncBranches : NamesToBranches) { llvm::stable_sort(FuncBranches.second.Data); + llvm::stable_sort(FuncBranches.second.EntryData); + } for (auto &MemEvents : NamesToMemEvents) llvm::stable_sort(MemEvents.second.Data); // Release intermediate storage. - clear(BranchLBRs); - clear(FallthroughLBRs); + clear(Traces); clear(BasicSamples); clear(MemSamples); } @@ -716,48 +742,54 @@ bool DataAggregator::doInterBranch(BinaryFunction *FromFunc, return true; } +bool DataAggregator::checkReturn(uint64_t Addr) { + auto isReturn = [&](auto MI) { return MI && BC->MIB->isReturn(*MI); }; + if (llvm::is_contained(Returns, Addr)) + return true; + + BinaryFunction *Func = getBinaryFunctionContainingAddress(Addr); + if (!Func) + return false; + + const uint64_t Offset = Addr - Func->getAddress(); + if (Func->hasInstructions() + ? isReturn(Func->getInstructionAtOffset(Offset)) + : isReturn(Func->disassembleInstructionAtOffset(Offset))) { + Returns.emplace(Addr); + return true; + } + return false; +} + bool DataAggregator::doBranch(uint64_t From, uint64_t To, uint64_t Count, uint64_t Mispreds) { - // Returns whether \p Offset in \p Func contains a return instruction. - auto checkReturn = [&](const BinaryFunction &Func, const uint64_t Offset) { - auto isReturn = [&](auto MI) { return MI && BC->MIB->isReturn(*MI); }; - return Func.hasInstructions() - ? isReturn(Func.getInstructionAtOffset(Offset)) - : isReturn(Func.disassembleInstructionAtOffset(Offset)); - }; - // Mutates \p Addr to an offset into the containing function, performing BAT // offset translation and parent lookup. // - // Returns the containing function (or BAT parent) and whether the address - // corresponds to a return (if \p IsFrom) or a call continuation (otherwise). + // Returns the containing function (or BAT parent). auto handleAddress = [&](uint64_t &Addr, bool IsFrom) { BinaryFunction *Func = getBinaryFunctionContainingAddress(Addr); - if (!Func) - return std::pair{Func, false}; + if (!Func) { + Addr = 0; + return Func; + } Addr -= Func->getAddress(); - bool IsRet = IsFrom && checkReturn(*Func, Addr); - if (BAT) Addr = BAT->translate(Func->getAddress(), Addr, IsFrom); if (BinaryFunction *ParentFunc = getBATParentFunction(*Func)) - Func = ParentFunc; + return ParentFunc; - return std::pair{Func, IsRet}; + return Func; }; - auto [FromFunc, IsReturn] = handleAddress(From, /*IsFrom*/ true); - auto [ToFunc, _] = handleAddress(To, /*IsFrom*/ false); + BinaryFunction *FromFunc = handleAddress(From, /*IsFrom*/ true); + BinaryFunction *ToFunc = handleAddress(To, /*IsFrom*/ false); if (!FromFunc && !ToFunc) return false; - // Ignore returns. - if (IsReturn) - return true; - // Treat recursive control transfers as inter-branches. if (FromFunc == ToFunc && To != 0) { recordBranch(*FromFunc, From, To, Count, Mispreds); @@ -767,37 +799,20 @@ bool DataAggregator::doBranch(uint64_t From, uint64_t To, uint64_t Count, return doInterBranch(FromFunc, ToFunc, From, To, Count, Mispreds); } -bool DataAggregator::doTrace(const LBREntry &First, const LBREntry &Second, - uint64_t Count) { - BinaryFunction *FromFunc = getBinaryFunctionContainingAddress(First.To); - BinaryFunction *ToFunc = getBinaryFunctionContainingAddress(Second.From); +bool DataAggregator::doTrace(const Trace &Trace, uint64_t Count, + bool IsReturn) { + const uint64_t From = Trace.From, To = Trace.To; + BinaryFunction *FromFunc = getBinaryFunctionContainingAddress(From); + BinaryFunction *ToFunc = getBinaryFunctionContainingAddress(To); + NumTraces += Count; if (!FromFunc || !ToFunc) { - LLVM_DEBUG({ - dbgs() << "Out of range trace starting in "; - if (FromFunc) - dbgs() << formatv("{0} @ {1:x}", *FromFunc, - First.To - FromFunc->getAddress()); - else - dbgs() << Twine::utohexstr(First.To); - dbgs() << " and ending in "; - if (ToFunc) - dbgs() << formatv("{0} @ {1:x}", *ToFunc, - Second.From - ToFunc->getAddress()); - else - dbgs() << Twine::utohexstr(Second.From); - dbgs() << '\n'; - }); + LLVM_DEBUG(dbgs() << "Out of range trace " << Trace << '\n'); NumLongRangeTraces += Count; return false; } if (FromFunc != ToFunc) { + LLVM_DEBUG(dbgs() << "Invalid trace " << Trace << '\n'); NumInvalidTraces += Count; - LLVM_DEBUG({ - dbgs() << "Invalid trace starting in " << FromFunc->getPrintName() - << formatv(" @ {0:x}", First.To - FromFunc->getAddress()) - << " and ending in " << ToFunc->getPrintName() - << formatv(" @ {0:x}\n", Second.From - ToFunc->getAddress()); - }); return false; } @@ -805,51 +820,37 @@ bool DataAggregator::doTrace(const LBREntry &First, const LBREntry &Second, BinaryFunction *ParentFunc = getBATParentFunction(*FromFunc); if (!ParentFunc) ParentFunc = FromFunc; - ParentFunc->SampleCountInBytes += Count * (Second.From - First.To); + ParentFunc->SampleCountInBytes += Count * (To - From); const uint64_t FuncAddress = FromFunc->getAddress(); std::optional FTs = BAT && BAT->isBATFunction(FuncAddress) - ? BAT->getFallthroughsInTrace(FuncAddress, First.To, Second.From) - : getFallthroughsInTrace(*FromFunc, First, Second, Count); + ? BAT->getFallthroughsInTrace(FuncAddress, From - IsReturn, To) + : getFallthroughsInTrace(*FromFunc, Trace, Count, IsReturn); if (!FTs) { - LLVM_DEBUG( - dbgs() << "Invalid trace starting in " << FromFunc->getPrintName() - << " @ " << Twine::utohexstr(First.To - FromFunc->getAddress()) - << " and ending in " << ToFunc->getPrintName() << " @ " - << ToFunc->getPrintName() << " @ " - << Twine::utohexstr(Second.From - ToFunc->getAddress()) << '\n'); + LLVM_DEBUG(dbgs() << "Invalid trace " << Trace << '\n'); NumInvalidTraces += Count; return false; } LLVM_DEBUG(dbgs() << "Processing " << FTs->size() << " fallthroughs for " - << FromFunc->getPrintName() << ":" - << Twine::utohexstr(First.To) << " to " - << Twine::utohexstr(Second.From) << ".\n"); - for (auto [From, To] : *FTs) { - if (BAT) { - From = BAT->translate(FromFunc->getAddress(), From, /*IsBranchSrc=*/true); - To = BAT->translate(FromFunc->getAddress(), To, /*IsBranchSrc=*/false); - } + << FromFunc->getPrintName() << ":" << Trace << '\n'); + for (const auto &[From, To] : *FTs) doIntraBranch(*ParentFunc, From, To, Count, false); - } return true; } std::optional, 16>> -DataAggregator::getFallthroughsInTrace(BinaryFunction &BF, - const LBREntry &FirstLBR, - const LBREntry &SecondLBR, - uint64_t Count) const { +DataAggregator::getFallthroughsInTrace(BinaryFunction &BF, const Trace &Trace, + uint64_t Count, bool IsReturn) const { SmallVector, 16> Branches; BinaryContext &BC = BF.getBinaryContext(); // Offsets of the trace within this function. - const uint64_t From = FirstLBR.To - BF.getAddress(); - const uint64_t To = SecondLBR.From - BF.getAddress(); + const uint64_t From = Trace.From - BF.getAddress(); + const uint64_t To = Trace.To - BF.getAddress(); if (From > To) return std::nullopt; @@ -876,8 +877,9 @@ DataAggregator::getFallthroughsInTrace(BinaryFunction &BF, // Adjust FromBB if the first LBR is a return from the last instruction in // the previous block (that instruction should be a call). - if (From == FromBB->getOffset() && !BF.containsAddress(FirstLBR.From) && - !FromBB->isEntryPoint() && !FromBB->isLandingPad()) { + if (Trace.Branch != Trace::FT_ONLY && !BF.containsAddress(Trace.Branch) && + From == FromBB->getOffset() && + (IsReturn ? From : !(FromBB->isEntryPoint() || FromBB->isLandingPad()))) { const BinaryBasicBlock *PrevBB = BF.getLayout().getBlock(FromBB->getIndex() - 1); if (PrevBB->getSuccessor(FromBB->getLabel())) { @@ -885,10 +887,9 @@ DataAggregator::getFallthroughsInTrace(BinaryFunction &BF, if (Instr && BC.MIB->isCall(*Instr)) FromBB = PrevBB; else - LLVM_DEBUG(dbgs() << "invalid incoming LBR (no call): " << FirstLBR - << '\n'); + LLVM_DEBUG(dbgs() << "invalid trace (no call): " << Trace << '\n'); } else { - LLVM_DEBUG(dbgs() << "invalid incoming LBR: " << FirstLBR << '\n'); + LLVM_DEBUG(dbgs() << "invalid trace: " << Trace << '\n'); } } @@ -907,9 +908,7 @@ DataAggregator::getFallthroughsInTrace(BinaryFunction &BF, // Check for bad LBRs. if (!BB->getSuccessor(NextBB->getLabel())) { - LLVM_DEBUG(dbgs() << "no fall-through for the trace:\n" - << " " << FirstLBR << '\n' - << " " << SecondLBR << '\n'); + LLVM_DEBUG(dbgs() << "no fall-through for the trace: " << Trace << '\n'); return std::nullopt; } @@ -972,7 +971,7 @@ bool DataAggregator::recordExit(BinaryFunction &BF, uint64_t From, bool Mispred, return true; } -ErrorOr DataAggregator::parseLBREntry() { +ErrorOr DataAggregator::parseLBREntry() { LBREntry Res; ErrorOr FromStrRes = parseString('/'); if (std::error_code EC = FromStrRes.getError()) @@ -998,9 +997,22 @@ ErrorOr DataAggregator::parseLBREntry() { if (std::error_code EC = MispredStrRes.getError()) return EC; StringRef MispredStr = MispredStrRes.get(); - if (MispredStr.size() != 1 || - (MispredStr[0] != 'P' && MispredStr[0] != 'M' && MispredStr[0] != '-')) { - reportError("expected single char for mispred bit"); + // SPE brstack mispredicted flags might be up to two characters long: + // 'PN' or 'MN'. Where 'N' optionally appears. + bool ValidStrSize = opts::ArmSPE + ? MispredStr.size() >= 1 && MispredStr.size() <= 2 + : MispredStr.size() == 1; + bool SpeTakenBitErr = + (opts::ArmSPE && MispredStr.size() == 2 && MispredStr[1] != 'N'); + bool PredictionBitErr = + !ValidStrSize || + (MispredStr[0] != 'P' && MispredStr[0] != 'M' && MispredStr[0] != '-'); + if (SpeTakenBitErr) + reportError("expected 'N' as SPE prediction bit for a not-taken branch"); + if (PredictionBitErr) + reportError("expected 'P', 'M' or '-' char as a prediction bit"); + + if (SpeTakenBitErr || PredictionBitErr) { Diag << "Found: " << MispredStr << "\n"; return make_error_code(llvm::errc::io_error); } @@ -1206,22 +1218,25 @@ ErrorOr DataAggregator::parseLocationOrOffset() { std::error_code DataAggregator::parseAggregatedLBREntry() { enum AggregatedLBREntry : char { INVALID = 0, - EVENT_NAME, // E - TRACE, // T - SAMPLE, // S - BRANCH, // B - FT, // F - FT_EXTERNAL_ORIGIN // f + EVENT_NAME, // E + TRACE, // T + RETURN, // R + SAMPLE, // S + BRANCH, // B + FT, // F + FT_EXTERNAL_ORIGIN, // f + FT_EXTERNAL_RETURN // r } Type = INVALID; - // The number of fields to parse, set based on Type. + /// The number of fields to parse, set based on \p Type. int AddrNum = 0; int CounterNum = 0; - // Storage for parsed fields. + /// Storage for parsed fields. StringRef EventName; std::optional Addr[3]; int64_t Counters[2] = {0}; + /// Parse strings: record type and optionally an event name. while (Type == INVALID || Type == EVENT_NAME) { while (checkAndConsumeFS()) { } @@ -1238,23 +1253,26 @@ std::error_code DataAggregator::parseAggregatedLBREntry() { Type = StringSwitch(Str) .Case("T", TRACE) + .Case("R", RETURN) .Case("S", SAMPLE) .Case("E", EVENT_NAME) .Case("B", BRANCH) .Case("F", FT) .Case("f", FT_EXTERNAL_ORIGIN) + .Case("r", FT_EXTERNAL_RETURN) .Default(INVALID); if (Type == INVALID) { - reportError("expected T, S, E, B, F or f"); + reportError("expected T, R, S, E, B, F, f or r"); return make_error_code(llvm::errc::io_error); } using SSI = StringSwitch; - AddrNum = SSI(Str).Case("T", 3).Case("S", 1).Case("E", 0).Default(2); + AddrNum = SSI(Str).Cases("T", "R", 3).Case("S", 1).Case("E", 0).Default(2); CounterNum = SSI(Str).Case("B", 2).Case("E", 0).Default(1); } + /// Parse locations depending on entry type, recording them in \p Addr array. for (int I = 0; I < AddrNum; ++I) { while (checkAndConsumeFS()) { } @@ -1264,6 +1282,7 @@ std::error_code DataAggregator::parseAggregatedLBREntry() { Addr[I] = AddrOrErr.get(); } + /// Parse counters depending on entry type. for (int I = 0; I < CounterNum; ++I) { while (checkAndConsumeFS()) { } @@ -1274,11 +1293,13 @@ std::error_code DataAggregator::parseAggregatedLBREntry() { Counters[I] = CountOrErr.get(); } + /// Expect end of line here. if (!checkAndConsumeNewLine()) { reportError("expected end of line"); return make_error_code(llvm::errc::io_error); } + /// Record event name into \p EventNames and return. if (Type == EVENT_NAME) { EventNames.insert(EventName); return std::error_code(); @@ -1292,6 +1313,7 @@ std::error_code DataAggregator::parseAggregatedLBREntry() { int64_t Count = Counters[0]; int64_t Mispreds = Counters[1]; + /// Record basic IP sample into \p BasicSamples and return. if (Type == SAMPLE) { BasicSamples[FromOffset] += Count; NumTotalSamples += Count; @@ -1303,30 +1325,39 @@ std::error_code DataAggregator::parseAggregatedLBREntry() { if (ToFunc) ToFunc->setHasProfileAvailable(); - Trace Trace(FromOffset, ToOffset); - // Taken trace - if (Type == TRACE || Type == BRANCH) { - TakenBranchInfo &Info = BranchLBRs[Trace]; - Info.TakenCount += Count; - Info.MispredCount += Mispreds; - - NumTotalSamples += Count; - } - // Construct fallthrough part of the trace - if (Type == TRACE) { - const uint64_t TraceFtEndOffset = Addr[2]->Offset; - Trace.From = ToOffset; - Trace.To = TraceFtEndOffset; - Type = FromFunc == ToFunc ? FT : FT_EXTERNAL_ORIGIN; + /// 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 + Addr[1] = Location(Addr[0]->Offset); // Trace From + // Put a magic value into Trace Branch to differentiate from a full trace: + if (Type == FT) + Addr[0] = Location(Trace::FT_ONLY); + else if (Type == FT_EXTERNAL_ORIGIN) + Addr[0] = Location(Trace::FT_EXTERNAL_ORIGIN); + else if (Type == FT_EXTERNAL_RETURN) + Addr[0] = Location(Trace::FT_EXTERNAL_RETURN); + else + llvm_unreachable("Unexpected fall-through type"); } - // Add fallthrough trace - if (Type != BRANCH) { - FTInfo &Info = FallthroughLBRs[Trace]; - (Type == FT ? Info.InternCount : Info.ExternCount) += Count; - NumTraces += Count; + /// For branch type, mark Trace To to differentiate from a full trace. + if (Type == BRANCH) + Addr[2] = Location(Trace::BR_ONLY); + + if (Type == RETURN) { + if (!Addr[0]->Offset) + Addr[0]->Offset = Trace::FT_EXTERNAL_RETURN; + else + Returns.emplace(Addr[0]->Offset); } + /// Record a trace. + Trace T{Addr[0]->Offset, Addr[1]->Offset, Addr[2]->Offset}; + TakenBranchInfo TI{(uint64_t)Count, (uint64_t)Mispreds}; + Traces.emplace_back(T, TI); + + NumTotalSamples += Count; + return std::error_code(); } @@ -1337,7 +1368,7 @@ bool DataAggregator::ignoreKernelInterrupt(LBREntry &LBR) const { std::error_code DataAggregator::printLBRHeatMap() { outs() << "PERF2BOLT: parse branch events...\n"; - NamedRegionTimer T("parseBranch", "Parsing branch events", TimerGroupName, + NamedRegionTimer T("buildHeatmap", "Building heatmap", TimerGroupName, TimerGroupDesc, opts::TimeAggregator); if (BC->IsLinuxKernel) { @@ -1373,12 +1404,9 @@ std::error_code DataAggregator::printLBRHeatMap() { // Register basic samples and perf LBR addresses not covered by fallthroughs. for (const auto &[PC, Hits] : BasicSamples) HM.registerAddress(PC, Hits); - for (const auto &LBR : FallthroughLBRs) { - const Trace &Trace = LBR.first; - const FTInfo &Info = LBR.second; - HM.registerAddressRange(Trace.From, Trace.To, - Info.InternCount + Info.ExternCount); - } + for (const auto &[Trace, Info] : Traces) + if (Trace.To != Trace::BR_ONLY) + HM.registerAddressRange(Trace.From, Trace.To, Info.TakenCount); if (HM.getNumInvalidRanges()) outs() << "HEATMAP: invalid traces: " << HM.getNumInvalidRanges() << '\n'; @@ -1424,60 +1452,10 @@ void DataAggregator::parseLBRSample(const PerfBranchSample &Sample, // chronological order) if (NeedsSkylakeFix && NumEntry <= 2) continue; - if (NextLBR) { - // Record fall-through trace. - const uint64_t TraceFrom = LBR.To; - const uint64_t TraceTo = NextLBR->From; - const BinaryFunction *TraceBF = - getBinaryFunctionContainingAddress(TraceFrom); - if (opts::HeatmapMode == opts::HeatmapModeKind::HM_Exclusive) { - FTInfo &Info = FallthroughLBRs[Trace(TraceFrom, TraceTo)]; - ++Info.InternCount; - } else if (TraceBF && TraceBF->containsAddress(TraceTo)) { - FTInfo &Info = FallthroughLBRs[Trace(TraceFrom, TraceTo)]; - if (TraceBF->containsAddress(LBR.From)) - ++Info.InternCount; - else - ++Info.ExternCount; - } else { - const BinaryFunction *ToFunc = - getBinaryFunctionContainingAddress(TraceTo); - if (TraceBF && ToFunc) { - LLVM_DEBUG({ - dbgs() << "Invalid trace starting in " << TraceBF->getPrintName() - << formatv(" @ {0:x}", TraceFrom - TraceBF->getAddress()) - << formatv(" and ending @ {0:x}\n", TraceTo); - }); - ++NumInvalidTraces; - } else { - LLVM_DEBUG({ - dbgs() << "Out of range trace starting in " - << (TraceBF ? TraceBF->getPrintName() : "None") - << formatv(" @ {0:x}", - TraceFrom - (TraceBF ? TraceBF->getAddress() : 0)) - << " and ending in " - << (ToFunc ? ToFunc->getPrintName() : "None") - << formatv(" @ {0:x}\n", - TraceTo - (ToFunc ? ToFunc->getAddress() : 0)); - }); - ++NumLongRangeTraces; - } - } - ++NumTraces; - } + uint64_t TraceTo = NextLBR ? NextLBR->From : Trace::BR_ONLY; NextLBR = &LBR; - // Record branches outside binary functions for heatmap. - if (opts::HeatmapMode == opts::HeatmapModeKind::HM_Exclusive) { - TakenBranchInfo &Info = BranchLBRs[Trace(LBR.From, LBR.To)]; - ++Info.TakenCount; - continue; - } - uint64_t From = getBinaryFunctionContainingAddress(LBR.From) ? LBR.From : 0; - uint64_t To = getBinaryFunctionContainingAddress(LBR.To) ? LBR.To : 0; - if (!From && !To) - continue; - TakenBranchInfo &Info = BranchLBRs[Trace(From, To)]; + TakenBranchInfo &Info = TraceMap[Trace{LBR.From, LBR.To, TraceTo}]; ++Info.TakenCount; Info.MispredCount += LBR.Mispred; } @@ -1552,7 +1530,9 @@ void DataAggregator::printBranchStacksDiagnostics( } std::error_code DataAggregator::parseBranchEvents() { - outs() << "PERF2BOLT: parse branch events...\n"; + std::string BranchEventTypeStr = + opts::ArmSPE ? "SPE branch events in LBR-format" : "branch events"; + outs() << "PERF2BOLT: parse " << BranchEventTypeStr << "...\n"; NamedRegionTimer T("parseBranch", "Parsing branch events", TimerGroupName, TimerGroupDesc, opts::TimeAggregator); @@ -1580,7 +1560,8 @@ std::error_code DataAggregator::parseBranchEvents() { } NumEntries += Sample.LBR.size(); - if (BAT && Sample.LBR.size() == 32 && !NeedsSkylakeFix) { + if (this->BC->isX86() && BAT && Sample.LBR.size() == 32 && + !NeedsSkylakeFix) { errs() << "PERF2BOLT-WARNING: using Intel Skylake bug workaround\n"; NeedsSkylakeFix = true; } @@ -1588,10 +1569,14 @@ std::error_code DataAggregator::parseBranchEvents() { parseLBRSample(Sample, NeedsSkylakeFix); } - for (const Trace &Trace : llvm::make_first_range(BranchLBRs)) - for (const uint64_t Addr : {Trace.From, Trace.To}) + 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); outs() << "PERF2BOLT: read " << NumSamples << " samples and " << NumEntries << " LBR entries\n"; @@ -1599,10 +1584,18 @@ std::error_code DataAggregator::parseBranchEvents() { if (NumSamples && NumSamplesNoLBR == NumSamples) { // Note: we don't know if perf2bolt is being used to parse memory samples // at this point. In this case, it is OK to parse zero LBRs. - errs() << "PERF2BOLT-WARNING: all recorded samples for this binary lack " - "LBR. Record profile with perf record -j any or run perf2bolt " - "in no-LBR mode with -nl (the performance improvement in -nl " - "mode may be limited)\n"; + if (!opts::ArmSPE) + errs() + << "PERF2BOLT-WARNING: all recorded samples for this binary lack " + "LBR. Record profile with perf record -j any or run perf2bolt " + "in no-LBR mode with -nl (the performance improvement in -nl " + "mode may be limited)\n"; + else + errs() + << "PERF2BOLT-WARNING: All recorded samples for this binary lack " + "SPE brstack entries. Make sure you are running Linux perf 6.14 " + "or later, otherwise you get zero samples. Record the profile " + "with: perf record -e 'arm_spe_0/branch_filter=1/'."; } else { printBranchStacksDiagnostics(NumTotalSamples - NumSamples); } @@ -1616,23 +1609,15 @@ void DataAggregator::processBranchEvents() { NamedRegionTimer T("processBranch", "Processing branch events", TimerGroupName, TimerGroupDesc, opts::TimeAggregator); - for (const auto &AggrLBR : FallthroughLBRs) { - const Trace &Loc = AggrLBR.first; - const FTInfo &Info = AggrLBR.second; - LBREntry First{Loc.From, Loc.From, false}; - LBREntry Second{Loc.To, Loc.To, false}; - if (Info.InternCount) - doTrace(First, Second, Info.InternCount); - if (Info.ExternCount) { - First.From = 0; - doTrace(First, Second, Info.ExternCount); - } - } - - for (const auto &AggrLBR : BranchLBRs) { - const Trace &Loc = AggrLBR.first; - const TakenBranchInfo &Info = AggrLBR.second; - doBranch(Loc.From, Loc.To, Info.TakenCount, Info.MispredCount); + Returns.emplace(Trace::FT_EXTERNAL_RETURN); + for (const auto &[Trace, Info] : Traces) { + bool IsReturn = checkReturn(Trace.Branch); + // Ignore returns. + if (!IsReturn && Trace.Branch != Trace::FT_ONLY && + Trace.Branch != Trace::FT_EXTERNAL_ORIGIN) + doBranch(Trace.Branch, Trace.From, Info.TakenCount, Info.MispredCount); + if (Trace.To != Trace::BR_ONLY) + doTrace(Trace, Info.TakenCount, IsReturn); } printBranchSamplesDiagnostics(); } @@ -2289,6 +2274,7 @@ std::error_code DataAggregator::writeBATYAML(BinaryContext &BC, YamlBF.Id = BF->getFunctionNumber(); YamlBF.Hash = BAT->getBFHash(FuncAddress); YamlBF.ExecCount = BF->getKnownExecutionCount(); + YamlBF.ExternEntryCount = BF->getExternEntryCount(); YamlBF.NumBasicBlocks = BAT->getNumBasicBlocks(FuncAddress); const BoltAddressTranslation::BBHashMapTy &BlockMap = BAT->getBBHashMap(FuncAddress); @@ -2398,16 +2384,10 @@ std::error_code DataAggregator::writeBATYAML(BinaryContext &BC, void DataAggregator::dump() const { DataReader::dump(); } -void DataAggregator::dump(const LBREntry &LBR) const { - Diag << "From: " << Twine::utohexstr(LBR.From) - << " To: " << Twine::utohexstr(LBR.To) << " Mispred? " << LBR.Mispred - << "\n"; -} - void DataAggregator::dump(const PerfBranchSample &Sample) const { Diag << "Sample LBR entries: " << Sample.LBR.size() << "\n"; for (const LBREntry &LBR : Sample.LBR) - dump(LBR); + Diag << LBR << '\n'; } void DataAggregator::dump(const PerfMemSample &Sample) const { diff --git a/bolt/lib/Profile/DataReader.cpp b/bolt/lib/Profile/DataReader.cpp index c512394f26a3..afe24216d7f5 100644 --- a/bolt/lib/Profile/DataReader.cpp +++ b/bolt/lib/Profile/DataReader.cpp @@ -85,6 +85,7 @@ void FuncBranchData::appendFrom(const FuncBranchData &FBD, uint64_t Offset) { } llvm::stable_sort(Data); ExecutionCount += FBD.ExecutionCount; + ExternEntryCount += FBD.ExternEntryCount; for (auto I = FBD.EntryData.begin(), E = FBD.EntryData.end(); I != E; ++I) { assert(I->To.Name == FBD.Name); auto NewElmt = EntryData.insert(EntryData.end(), *I); @@ -269,6 +270,7 @@ Error DataReader::preprocessProfile(BinaryContext &BC) { if (FuncBranchData *FuncData = getBranchDataForNames(Function.getNames())) { setBranchData(Function, FuncData); Function.ExecutionCount = FuncData->ExecutionCount; + Function.ExternEntryCount = FuncData->ExternEntryCount; FuncData->Used = true; } } @@ -419,6 +421,7 @@ void DataReader::matchProfileData(BinaryFunction &BF) { if (fetchProfileForOtherEntryPoints(BF)) { BF.ProfileMatchRatio = evaluateProfileData(BF, *FBD); BF.ExecutionCount = FBD->ExecutionCount; + BF.ExternEntryCount = FBD->ExternEntryCount; BF.RawSampleCount = FBD->getNumExecutedBranches(); } return; @@ -449,6 +452,7 @@ void DataReader::matchProfileData(BinaryFunction &BF) { setBranchData(BF, NewBranchData); NewBranchData->Used = true; BF.ExecutionCount = NewBranchData->ExecutionCount; + BF.ExternEntryCount = NewBranchData->ExternEntryCount; BF.ProfileMatchRatio = 1.0f; break; } @@ -1190,6 +1194,8 @@ std::error_code DataReader::parse() { if (BI.To.IsSymbol && BI.To.Offset == 0) { I = GetOrCreateFuncEntry(BI.To.Name); I->second.ExecutionCount += BI.Branches; + if (!BI.From.IsSymbol) + I->second.ExternEntryCount += BI.Branches; } } diff --git a/bolt/lib/Profile/YAMLProfileReader.cpp b/bolt/lib/Profile/YAMLProfileReader.cpp index 33ce40ac2eee..086e47b661e1 100644 --- a/bolt/lib/Profile/YAMLProfileReader.cpp +++ b/bolt/lib/Profile/YAMLProfileReader.cpp @@ -176,6 +176,7 @@ bool YAMLProfileReader::parseFunctionProfile( uint64_t FunctionExecutionCount = 0; BF.setExecutionCount(YamlBF.ExecCount); + BF.setExternEntryCount(YamlBF.ExternEntryCount); uint64_t FuncRawBranchCount = 0; for (const yaml::bolt::BinaryBasicBlockProfile &YamlBB : YamlBF.Blocks) diff --git a/bolt/lib/Profile/YAMLProfileWriter.cpp b/bolt/lib/Profile/YAMLProfileWriter.cpp index f1fe45f21a0f..1632aa1c6bfe 100644 --- a/bolt/lib/Profile/YAMLProfileWriter.cpp +++ b/bolt/lib/Profile/YAMLProfileWriter.cpp @@ -226,6 +226,7 @@ YAMLProfileWriter::convert(const BinaryFunction &BF, bool UseDFS, YamlBF.Hash = BF.getHash(); YamlBF.NumBasicBlocks = BF.size(); YamlBF.ExecCount = BF.getKnownExecutionCount(); + YamlBF.ExternEntryCount = BF.getExternEntryCount(); DenseMap InlineTreeNodeId; if (PseudoProbeDecoder && BF.getGUID()) { std::tie(YamlBF.InlineTree, InlineTreeNodeId) = @@ -303,9 +304,8 @@ YAMLProfileWriter::convert(const BinaryFunction &BF, bool UseDFS, } // Sort targets in a similar way to getBranchData, see Location::operator< llvm::sort(CSTargets, [](const auto &RHS, const auto &LHS) { - if (RHS.first != LHS.first) - return RHS.first < LHS.first; - return RHS.second.Offset < LHS.second.Offset; + return std::tie(RHS.first, RHS.second.Offset) < + std::tie(LHS.first, LHS.second.Offset); }); for (auto &KV : CSTargets) YamlBB.CallSites.push_back(KV.second); diff --git a/bolt/lib/Rewrite/LinuxKernelRewriter.cpp b/bolt/lib/Rewrite/LinuxKernelRewriter.cpp index 5a5e044184d0..174721a3a053 100644 --- a/bolt/lib/Rewrite/LinuxKernelRewriter.cpp +++ b/bolt/lib/Rewrite/LinuxKernelRewriter.cpp @@ -432,25 +432,33 @@ class LinuxKernelRewriter final : public MetadataRewriter { }; Error LinuxKernelRewriter::detectLinuxKernelVersion() { - if (BinaryData *BD = BC.getBinaryDataByName("linux_banner")) { - const BinarySection &Section = BD->getSection(); - const std::string S = - Section.getContents().substr(BD->getOffset(), BD->getSize()).str(); - - const std::regex Re(R"---(Linux version ((\d+)\.(\d+)(\.(\d+))?))---"); - std::smatch Match; - if (std::regex_search(S, Match, Re)) { - const unsigned Major = std::stoi(Match[2].str()); - const unsigned Minor = std::stoi(Match[3].str()); - const unsigned Rev = Match[5].matched ? std::stoi(Match[5].str()) : 0; - LinuxKernelVersion = LKVersion(Major, Minor, Rev); - BC.outs() << "BOLT-INFO: Linux kernel version is " << Match[1].str() - << "\n"; - return Error::success(); - } + // Check for global and local linux_banner symbol. + BinaryData *BD = BC.getBinaryDataByName("linux_banner"); + if (!BD) + BD = BC.getBinaryDataByName("linux_banner/1"); + + if (!BD) + return createStringError(errc::executable_format_error, + "unable to locate linux_banner"); + + const BinarySection &Section = BD->getSection(); + const std::string S = + Section.getContents().substr(BD->getOffset(), BD->getSize()).str(); + + const std::regex Re(R"---(Linux version ((\d+)\.(\d+)(\.(\d+))?))---"); + std::smatch Match; + if (std::regex_search(S, Match, Re)) { + const unsigned Major = std::stoi(Match[2].str()); + const unsigned Minor = std::stoi(Match[3].str()); + const unsigned Rev = Match[5].matched ? std::stoi(Match[5].str()) : 0; + LinuxKernelVersion = LKVersion(Major, Minor, Rev); + BC.outs() << "BOLT-INFO: Linux kernel version is " << Match[1].str() + << "\n"; + return Error::success(); } + return createStringError(errc::executable_format_error, - "Linux kernel version is unknown"); + "Linux kernel version is unknown: " + S); } void LinuxKernelRewriter::processLKSections() { diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp index e1aa00a3d749..93bd93b6cb98 100644 --- a/bolt/lib/Rewrite/RewriteInstance.cpp +++ b/bolt/lib/Rewrite/RewriteInstance.cpp @@ -780,14 +780,6 @@ void RewriteInstance::discoverFileObjects() { // For local symbols we want to keep track of associated FILE symbol name for // disambiguation by combined name. - StringRef FileSymbolName; - bool SeenFileName = false; - struct SymbolRefHash { - size_t operator()(SymbolRef const &S) const { - return std::hash{}(S.getRawDataRefImpl().p); - } - }; - std::unordered_map SymbolToFileName; for (const ELFSymbolRef &Symbol : InputFile->symbols()) { Expected NameOrError = Symbol.getName(); if (NameOrError && NameOrError->starts_with("__asan_init")) { @@ -806,21 +798,8 @@ void RewriteInstance::discoverFileObjects() { if (cantFail(Symbol.getFlags()) & SymbolRef::SF_Undefined) continue; - if (cantFail(Symbol.getType()) == SymbolRef::ST_File) { + if (cantFail(Symbol.getType()) == SymbolRef::ST_File) FileSymbols.emplace_back(Symbol); - StringRef Name = - cantFail(std::move(NameOrError), "cannot get symbol name for file"); - // Ignore Clang LTO artificial FILE symbol as it is not always generated, - // and this uncertainty is causing havoc in function name matching. - if (Name == "ld-temp.o") - continue; - FileSymbolName = Name; - SeenFileName = true; - continue; - } - if (!FileSymbolName.empty() && - !(cantFail(Symbol.getFlags()) & SymbolRef::SF_Global)) - SymbolToFileName[Symbol] = FileSymbolName; } // Sort symbols in the file by value. Ignore symbols from non-allocatable @@ -1028,14 +1007,14 @@ void RewriteInstance::discoverFileObjects() { // The field is used for disambiguation of local symbols since there // could be identical function names coming from identical file names // (e.g. from different directories). - std::string AltPrefix; - auto SFI = SymbolToFileName.find(Symbol); - if (SymbolType == SymbolRef::ST_Function && SFI != SymbolToFileName.end()) - AltPrefix = Name + "/" + std::string(SFI->second); + auto SFI = llvm::upper_bound(FileSymbols, ELFSymbolRef(Symbol)); + if (SymbolType == SymbolRef::ST_Function && SFI != FileSymbols.begin()) { + StringRef FileSymbolName = cantFail(SFI[-1].getName()); + if (!FileSymbolName.empty()) + AlternativeName = NR.uniquify(Name + "/" + FileSymbolName.str()); + } UniqueName = NR.uniquify(Name); - if (!AltPrefix.empty()) - AlternativeName = NR.uniquify(AltPrefix); } uint64_t SymbolSize = ELFSymbolRef(Symbol).getSize(); @@ -1294,7 +1273,7 @@ void RewriteInstance::discoverFileObjects() { FDE->getAddressRange()); } - BC->setHasSymbolsWithFileName(SeenFileName); + BC->setHasSymbolsWithFileName(FileSymbols.size()); // Now that all the functions were created - adjust their boundaries. adjustFunctionBoundaries(); @@ -1567,6 +1546,11 @@ void RewriteInstance::registerFragments() { uint64_t ParentAddress{0}; + // Check if containing FILE symbol is BOLT emitted synthetic symbol marking + // local fragments of global parents. + if (cantFail(FSI[-1].getName()) == getBOLTFileSymbolName()) + goto registerParent; + // BOLT split fragment symbols are emitted just before the main function // symbol. for (ELFSymbolRef NextSymbol = Symbol; NextSymbol < StopSymbol; diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index 9d5a578cfbdf..e6e0aeba3457 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -14,7 +14,7 @@ #include "AArch64MCSymbolizer.h" #include "MCTargetDesc/AArch64AddressingModes.h" #include "MCTargetDesc/AArch64FixupKinds.h" -#include "MCTargetDesc/AArch64MCExpr.h" +#include "MCTargetDesc/AArch64MCAsmInfo.h" #include "MCTargetDesc/AArch64MCTargetDesc.h" #include "Utils/AArch64BaseInfo.h" #include "bolt/Core/BinaryBasicBlock.h" @@ -177,15 +177,12 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { return true; } - bool equals(const MCTargetExpr &A, const MCTargetExpr &B, + bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B, CompFuncTy Comp) const override { - const auto &AArch64ExprA = cast(A); - const auto &AArch64ExprB = cast(B); - if (AArch64ExprA.getKind() != AArch64ExprB.getKind()) + if (A.getSpecifier() != B.getSpecifier()) return false; - return MCPlusBuilder::equals(*AArch64ExprA.getSubExpr(), - *AArch64ExprB.getSubExpr(), Comp); + return MCPlusBuilder::equals(*A.getSubExpr(), *B.getSubExpr(), Comp); } bool shortenInstruction(MCInst &, const MCSubtargetInfo &) const override { @@ -1084,7 +1081,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { if (isADR(Inst) || RelType == ELF::R_AARCH64_ADR_PREL_LO21 || RelType == ELF::R_AARCH64_TLSDESC_ADR_PREL21) { - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS, Ctx); + return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS, Ctx); } else if (isADRP(Inst) || RelType == ELF::R_AARCH64_ADR_PREL_PG_HI21 || RelType == ELF::R_AARCH64_ADR_PREL_PG_HI21_NC || RelType == ELF::R_AARCH64_TLSDESC_ADR_PAGE21 || @@ -1092,7 +1089,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { RelType == ELF::R_AARCH64_ADR_GOT_PAGE) { // Never emit a GOT reloc, we handled this in // RewriteInstance::readRelocations(). - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_PAGE, Ctx); + return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_PAGE, Ctx); } else { switch (RelType) { case ELF::R_AARCH64_ADD_ABS_LO12_NC: @@ -1106,18 +1103,18 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { case ELF::R_AARCH64_TLSDESC_LD64_LO12: case ELF::R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: case ELF::R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_LO12, Ctx); + return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_LO12, Ctx); case ELF::R_AARCH64_MOVW_UABS_G3: - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G3, Ctx); + return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G3, Ctx); case ELF::R_AARCH64_MOVW_UABS_G2: case ELF::R_AARCH64_MOVW_UABS_G2_NC: - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G2_NC, Ctx); + return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G2_NC, Ctx); case ELF::R_AARCH64_MOVW_UABS_G1: case ELF::R_AARCH64_MOVW_UABS_G1_NC: - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G1_NC, Ctx); + return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G1_NC, Ctx); case ELF::R_AARCH64_MOVW_UABS_G0: case ELF::R_AARCH64_MOVW_UABS_G0_NC: - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G0_NC, Ctx); + return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G0_NC, Ctx); default: break; } @@ -1142,7 +1139,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { } const MCSymbol *getTargetSymbol(const MCExpr *Expr) const override { - auto *AArchExpr = dyn_cast(Expr); + auto *AArchExpr = dyn_cast(Expr); if (AArchExpr && AArchExpr->getSubExpr()) return getTargetSymbol(AArchExpr->getSubExpr()); @@ -1162,7 +1159,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { } int64_t getTargetAddend(const MCExpr *Expr) const override { - auto *AArchExpr = dyn_cast(Expr); + auto *AArchExpr = dyn_cast(Expr); if (AArchExpr && AArchExpr->getSubExpr()) return getTargetAddend(AArchExpr->getSubExpr()); @@ -2030,9 +2027,8 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { MCInst Inst; Inst.setOpcode(AArch64::MOVZXi); Inst.addOperand(MCOperand::createReg(AArch64::X16)); - Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - AArch64MCExpr::VK_ABS_G3, *Ctx))); + Inst.addOperand(MCOperand::createExpr( + MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G3, *Ctx))); Inst.addOperand(MCOperand::createImm(0x30)); Seq.emplace_back(Inst); @@ -2040,9 +2036,8 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { Inst.setOpcode(AArch64::MOVKXi); Inst.addOperand(MCOperand::createReg(AArch64::X16)); Inst.addOperand(MCOperand::createReg(AArch64::X16)); - Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - AArch64MCExpr::VK_ABS_G2_NC, *Ctx))); + Inst.addOperand(MCOperand::createExpr( + MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G2_NC, *Ctx))); Inst.addOperand(MCOperand::createImm(0x20)); Seq.emplace_back(Inst); @@ -2050,9 +2045,8 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { Inst.setOpcode(AArch64::MOVKXi); Inst.addOperand(MCOperand::createReg(AArch64::X16)); Inst.addOperand(MCOperand::createReg(AArch64::X16)); - Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - AArch64MCExpr::VK_ABS_G1_NC, *Ctx))); + Inst.addOperand(MCOperand::createExpr( + MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G1_NC, *Ctx))); Inst.addOperand(MCOperand::createImm(0x10)); Seq.emplace_back(Inst); @@ -2060,9 +2054,8 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { Inst.setOpcode(AArch64::MOVKXi); Inst.addOperand(MCOperand::createReg(AArch64::X16)); Inst.addOperand(MCOperand::createReg(AArch64::X16)); - Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - AArch64MCExpr::VK_ABS_G0_NC, *Ctx))); + Inst.addOperand(MCOperand::createExpr( + MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G0_NC, *Ctx))); Inst.addOperand(MCOperand::createImm(0)); Seq.emplace_back(Inst); diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp index 581f22f2a2b7..c7d664ab09d4 100644 --- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp +++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -#include "MCTargetDesc/RISCVMCExpr.h" +#include "MCTargetDesc/RISCVMCAsmInfo.h" #include "MCTargetDesc/RISCVMCTargetDesc.h" #include "bolt/Core/MCPlusBuilder.h" #include "llvm/BinaryFormat/ELF.h" @@ -31,10 +31,10 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { public: using MCPlusBuilder::MCPlusBuilder; - bool equals(const MCTargetExpr &A, const MCTargetExpr &B, + bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B, CompFuncTy Comp) const override { - const auto &RISCVExprA = cast(A); - const auto &RISCVExprB = cast(B); + const auto &RISCVExprA = cast(A); + const auto &RISCVExprB = cast(B); if (RISCVExprA.getSpecifier() != RISCVExprB.getSpecifier()) return false; @@ -245,7 +245,7 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { MCContext *Ctx) { Inst.setOpcode(Opcode); Inst.clear(); - Inst.addOperand(MCOperand::createExpr(RISCVMCExpr::create( + Inst.addOperand(MCOperand::createExpr(MCSpecifierExpr::create( MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), ELF::R_RISCV_CALL_PLT, *Ctx))); } @@ -342,7 +342,7 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { } const MCSymbol *getTargetSymbol(const MCExpr *Expr) const override { - auto *RISCVExpr = dyn_cast(Expr); + auto *RISCVExpr = dyn_cast(Expr); if (RISCVExpr && RISCVExpr->getSubExpr()) return getTargetSymbol(RISCVExpr->getSubExpr()); @@ -435,19 +435,19 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { case ELF::R_RISCV_TLS_GD_HI20: // The GOT is reused so no need to create GOT relocations case ELF::R_RISCV_PCREL_HI20: - return RISCVMCExpr::create(Expr, ELF::R_RISCV_PCREL_HI20, Ctx); + return MCSpecifierExpr::create(Expr, ELF::R_RISCV_PCREL_HI20, Ctx); case ELF::R_RISCV_PCREL_LO12_I: case ELF::R_RISCV_PCREL_LO12_S: - return RISCVMCExpr::create(Expr, RISCVMCExpr::VK_PCREL_LO, Ctx); + return MCSpecifierExpr::create(Expr, RISCV::S_PCREL_LO, Ctx); case ELF::R_RISCV_HI20: - return RISCVMCExpr::create(Expr, ELF::R_RISCV_HI20, Ctx); + return MCSpecifierExpr::create(Expr, ELF::R_RISCV_HI20, Ctx); case ELF::R_RISCV_LO12_I: case ELF::R_RISCV_LO12_S: - return RISCVMCExpr::create(Expr, RISCVMCExpr::VK_LO, Ctx); + return MCSpecifierExpr::create(Expr, RISCV::S_LO, Ctx); case ELF::R_RISCV_CALL: - return RISCVMCExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx); + return MCSpecifierExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx); case ELF::R_RISCV_CALL_PLT: - return RISCVMCExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx); + return MCSpecifierExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx); } } @@ -466,10 +466,10 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { return false; const auto *ImmExpr = ImmOp.getExpr(); - if (!isa(ImmExpr)) + if (!isa(ImmExpr)) return false; - switch (cast(ImmExpr)->getSpecifier()) { + switch (cast(ImmExpr)->getSpecifier()) { default: return false; case ELF::R_RISCV_CALL_PLT: diff --git a/bolt/lib/Utils/CMakeLists.txt b/bolt/lib/Utils/CMakeLists.txt index efba6d54449d..94933644ef5e 100644 --- a/bolt/lib/Utils/CMakeLists.txt +++ b/bolt/lib/Utils/CMakeLists.txt @@ -6,12 +6,25 @@ set(version_inc "${CMAKE_CURRENT_BINARY_DIR}/VCSVersion.inc") set(generate_vcs_version_script "${LLVM_CMAKE_DIR}/GenerateVersionFromVCS.cmake") +if(llvm_vc AND LLVM_APPEND_VC_REV) + set(llvm_source_dir ${LLVM_MAIN_SRC_DIR}) +endif() +if(LLVM_VC_REPOSITORY AND LLVM_VC_REVISION) + set(llvm_source_dir ${LLVM_SOURCE_DIR}) + set(llvm_vc_repository ${LLVM_VC_REPOSITORY}) + set(llvm_vc_revision ${LLVM_VC_REVISION}) +endif() +if(bolt_vc AND LLVM_APPEND_VC_REV) + set(bolt_source_dir ${BOLT_SOURCE_DIR}) +endif() + # Create custom target to generate the VC revision include. add_custom_command(OUTPUT "${version_inc}" DEPENDS "${llvm_vc}" "${bolt_vc}" "${generate_vcs_version_script}" COMMAND ${CMAKE_COMMAND} "-DNAMES=BOLT" + "-DLLVM_SOURCE_DIR=${llvm_source_dir}" + "-DBOLT_SOURCE_DIR=${bolt_source_dir}" "-DHEADER_FILE=${version_inc}" - "-DBOLT_SOURCE_DIR=${BOLT_SOURCE_DIR}" "-DLLVM_VC_REPOSITORY=${llvm_vc_repository}" "-DLLVM_VC_REVISION=${llvm_vc_revision}" "-DLLVM_FORCE_VC_REVISION=${LLVM_FORCE_VC_REVISION}" diff --git a/bolt/test/AArch64/adr-relaxation.s b/bolt/test/AArch64/adr-relaxation.s index a643a62339ba..864650c3287d 100644 --- a/bolt/test/AArch64/adr-relaxation.s +++ b/bolt/test/AArch64/adr-relaxation.s @@ -34,7 +34,6 @@ foo: .cfi_startproc cmp x1, x11 b.hi .L2 - mov x0, #0x0 .L2: # CHECK-FOO: : # CHECK-FOO-NEXT: adrp diff --git a/bolt/test/AArch64/r_aarch64_prelxx.s b/bolt/test/AArch64/r_aarch64_prelxx.s index 5cbe2c50b294..39f74301cedf 100644 --- a/bolt/test/AArch64/r_aarch64_prelxx.s +++ b/bolt/test/AArch64/r_aarch64_prelxx.s @@ -5,7 +5,7 @@ // REQUIRES: system-linux // RUN: %clang %cflags -nostartfiles -nostdlib %s -o %t.exe -mlittle-endian \ -// RUN: -Wl,-q -Wl,-z,max-page-size=4 +// RUN: -Wl,-q -Wl,-z,max-page-size=4 -Wl,--no-relax // RUN: llvm-readelf -Wa %t.exe | FileCheck %s -check-prefix=CHECKPREL // CHECKPREL: R_AARCH64_PREL16 {{.*}} .dummy + 0 @@ -36,9 +36,9 @@ .type _start, %function _start: adrp x0, datatable - add x0, x0, :lo12:datable + add x0, x0, :lo12:datatable mov x0, #0 - ret + ret .section .dummy, "a", @progbits dummy: diff --git a/bolt/test/X86/callcont-fallthru.s b/bolt/test/X86/callcont-fallthru.s index 4994cfb541ee..8c05491e7bca 100644 --- a/bolt/test/X86/callcont-fallthru.s +++ b/bolt/test/X86/callcont-fallthru.s @@ -4,29 +4,62 @@ # RUN: %clang %cflags -fpic -shared -xc /dev/null -o %t.so ## Link against a DSO to ensure PLT entries. # RUN: %clangxx %cxxflags %s %t.so -o %t -Wl,-q -nostdlib -# RUN: link_fdata %s %t %t.pat PREAGGT1 -# RUN: link_fdata %s %t %t.pat2 PREAGGT2 -# RUN-DISABLED: link_fdata %s %t %t.patplt PREAGGPLT +# Trace to a call continuation, not a landing pad/entry point +# RUN: link_fdata %s %t %t.pa-base PREAGG-BASE +# Trace from a return to a landing pad/entry point call continuation +# RUN: link_fdata %s %t %t.pa-ret PREAGG-RET +# Trace from an external location to a landing pad/entry point call continuation +# RUN: link_fdata %s %t %t.pa-ext PREAGG-EXT +# Return trace to a landing pad/entry point call continuation +# RUN: link_fdata %s %t %t.pa-pret PREAGG-PRET +# External return to a landing pad/entry point call continuation +# RUN: link_fdata %s %t %t.pa-eret PREAGG-ERET +# RUN-DISABLED: link_fdata %s %t %t.pa-plt PREAGG-PLT # RUN: llvm-strip --strip-unneeded %t -o %t.strip # RUN: llvm-objcopy --remove-section=.eh_frame %t.strip %t.noeh ## Check pre-aggregated traces attach call continuation fallthrough count -# RUN: llvm-bolt %t.noeh --pa -p %t.pat -o %t.out \ -# RUN: --print-cfg --print-only=main | FileCheck %s - -## Check pre-aggregated traces don't attach call continuation fallthrough count -## to secondary entry point (unstripped) -# RUN: llvm-bolt %t --pa -p %t.pat2 -o %t.out \ -# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK3 -## Check pre-aggregated traces don't attach call continuation fallthrough count -## to landing pad (stripped, LP) -# RUN: llvm-bolt %t.strip --pa -p %t.pat2 -o %t.out \ -# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK3 +## in the basic case (not an entry point, not a landing pad). +# RUN: llvm-bolt %t.noeh --pa -p %t.pa-base -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-BASE + +## Check pre-aggregated traces from a return attach call continuation +## fallthrough count to secondary entry point (unstripped) +# RUN: llvm-bolt %t --pa -p %t.pa-ret -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-ATTACH +## Check pre-aggregated traces from a return attach call continuation +## fallthrough count to landing pad (stripped, landing pad) +# RUN: llvm-bolt %t.strip --pa -p %t.pa-ret -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-ATTACH + +## Check pre-aggregated traces from external location don't attach call +## continuation fallthrough count to secondary entry point (unstripped) +# RUN: llvm-bolt %t --pa -p %t.pa-ext -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-SKIP +## Check pre-aggregated traces from external location don't attach call +## continuation fallthrough count to landing pad (stripped, landing pad) +# RUN: llvm-bolt %t.strip --pa -p %t.pa-ext -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-SKIP + +## Check pre-aggregated return traces from external location attach call +## continuation fallthrough count to secondary entry point (unstripped) +# RUN: llvm-bolt %t --pa -p %t.pa-pret -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-ATTACH +## Check pre-aggregated return traces from external location attach call +## continuation fallthrough count to landing pad (stripped, landing pad) +# RUN: llvm-bolt %t.strip --pa -p %t.pa-pret -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-ATTACH + +## Same for external return type +# RUN: llvm-bolt %t --pa -p %t.pa-eret -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-ATTACH +# RUN: llvm-bolt %t.strip --pa -p %t.pa-eret -o %t.out \ +# RUN: --print-cfg --print-only=main | FileCheck %s --check-prefix=CHECK-ATTACH ## Check pre-aggregated traces don't report zero-sized PLT fall-through as ## invalid trace -# RUN-DISABLED: llvm-bolt %t.strip --pa -p %t.patplt -o %t.out | FileCheck %s \ +# RUN-DISABLED: llvm-bolt %t.strip --pa -p %t.pa-plt -o %t.out | FileCheck %s \ # RUN-DISABLED: --check-prefix=CHECK-PLT # CHECK-PLT: traces mismatching disassembled function contents: 0 @@ -56,11 +89,11 @@ main: Ltmp0_br: callq puts@PLT ## Check PLT traces are accepted -# PREAGGPLT: T #Ltmp0_br# #puts@plt# #puts@plt# 3 +# PREAGG-PLT: T #Ltmp0_br# #puts@plt# #puts@plt# 3 ## Target is an external-origin call continuation -# PREAGGT1: T X:0 #Ltmp1# #Ltmp4_br# 2 -# CHECK: callq puts@PLT -# CHECK-NEXT: count: 2 +# PREAGG-BASE: T X:0 #Ltmp1# #Ltmp4_br# 2 +# CHECK-BASE: callq puts@PLT +# CHECK-BASE-NEXT: count: 2 Ltmp1: movq -0x10(%rbp), %rax @@ -71,24 +104,22 @@ Ltmp4: cmpl $0x0, -0x14(%rbp) Ltmp4_br: je Ltmp0 -# CHECK2: je .Ltmp0 -# CHECK2-NEXT: count: 3 movl $0xa, -0x18(%rbp) callq foo ## Target is a binary-local call continuation -# PREAGGT1: T #Lfoo_ret# #Ltmp3# #Ltmp3_br# 1 -# CHECK: callq foo -# CHECK-NEXT: count: 1 - -## PLT call continuation fallthrough spanning the call -# CHECK2: callq foo -# CHECK2-NEXT: count: 3 - +# PREAGG-RET: T #Lfoo_ret# #Ltmp3# #Ltmp3_br# 1 ## Target is a secondary entry point (unstripped) or a landing pad (stripped) -# PREAGGT2: T X:0 #Ltmp3# #Ltmp3_br# 2 -# CHECK3: callq foo -# CHECK3-NEXT: count: 0 +# PREAGG-EXT: T X:0 #Ltmp3# #Ltmp3_br# 1 +## Pre-aggregated return trace +# PREAGG-PRET: R X:0 #Ltmp3# #Ltmp3_br# 1 +## External return +# PREAGG-ERET: r #Ltmp3# #Ltmp3_br# 1 + +# CHECK-ATTACH: callq foo +# CHECK-ATTACH-NEXT: count: 1 +# CHECK-SKIP: callq foo +# CHECK-SKIP-NEXT: count: 0 Ltmp3: cmpl $0x0, -0x18(%rbp) diff --git a/bolt/test/X86/linux-version.S b/bolt/test/X86/linux-version.S index e680d0d64a21..a3d7f365304a 100644 --- a/bolt/test/X86/linux-version.S +++ b/bolt/test/X86/linux-version.S @@ -17,6 +17,11 @@ # RUN: -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr # RUN: llvm-bolt %t.exe -o %t.out 2>&1 | FileCheck --check-prefix=CHECK-C %s +# RUN: %clang -DD -target x86_64-unknown-unknown \ +# RUN: %cflags -nostdlib %s -o %t.exe \ +# RUN: -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr +# RUN: llvm-bolt %t.exe -o %t.out 2>&1 | FileCheck --check-prefix=CHECK-D %s + .text .globl foo .type foo, %function @@ -46,6 +51,12 @@ linux_banner: #endif # CHECK-C: BOLT-INFO: Linux kernel version is 6.6 +#ifdef D + .hidden linux_banner + .string "Linux version 6.6.15.2-2-xxx\n" +#endif +# CHECK-D: BOLT-INFO: Linux kernel version is 6.6 + .size linux_banner, . - linux_banner ## Fake Linux Kernel sections. diff --git a/bolt/test/X86/pre-aggregated-perf.test b/bolt/test/X86/pre-aggregated-perf.test index 92e093c238e0..c4f5b8f2ea44 100644 --- a/bolt/test/X86/pre-aggregated-perf.test +++ b/bolt/test/X86/pre-aggregated-perf.test @@ -36,26 +36,26 @@ RUN: llvm-bolt %t.exe -p %p/Inputs/pre-aggregated.txt --pa -o %t.null | FileChec CHECK: BOLT-INFO: 4 out of 7 functions in the binary (57.1%) have non-empty execution profile -RUN: cat %t | sort | FileCheck %s -check-prefix=PERF2BOLT -RUN: cat %t.new | FileCheck %s -check-prefix=NEWFORMAT +RUN: FileCheck %s -check-prefix=PERF2BOLT --input-file %t +RUN: FileCheck %s -check-prefix=NEWFORMAT --input-file %t.new ## Test --profile-format option with perf2bolt RUN: perf2bolt %t.exe -o %t.fdata --pa -p %p/Inputs/pre-aggregated.txt \ RUN: --profile-format=fdata -RUN: cat %t.fdata | sort | FileCheck %s -check-prefix=PERF2BOLT +RUN: FileCheck %s -check-prefix=PERF2BOLT --input-file %t.fdata RUN: perf2bolt %t.exe -o %t.yaml --pa -p %p/Inputs/pre-aggregated.txt \ RUN: --profile-format=yaml --profile-use-dfs -RUN: cat %t.yaml | FileCheck %s -check-prefix=NEWFORMAT +RUN: FileCheck %s -check-prefix=NEWFORMAT --input-file %t.yaml ## Test --profile-format option with llvm-bolt --aggregate-only RUN: llvm-bolt %t.exe -o %t.bolt.fdata --pa -p %p/Inputs/pre-aggregated.txt \ RUN: --aggregate-only --profile-format=fdata -RUN: cat %t.bolt.fdata | sort | FileCheck %s -check-prefix=PERF2BOLT +RUN: FileCheck %s -check-prefix=PERF2BOLT --input-file %t.bolt.fdata RUN: llvm-bolt %t.exe -o %t.bolt.yaml --pa -p %p/Inputs/pre-aggregated.txt \ RUN: --aggregate-only --profile-format=yaml --profile-use-dfs -RUN: cat %t.bolt.yaml | FileCheck %s -check-prefix=NEWFORMAT +RUN: FileCheck %s -check-prefix=NEWFORMAT --input-file %t.bolt.yaml ## Test pre-aggregated basic profile RUN: perf2bolt %t.exe -o %t --pa -p %p/Inputs/pre-aggregated-basic.txt -o %t.ba \ @@ -67,16 +67,17 @@ BASIC-ERROR: BOLT-INFO: 0 out of 7 functions in the binary (0.0%) have non-empty BASIC-SUCCESS: BOLT-INFO: 4 out of 7 functions in the binary (57.1%) have non-empty execution profile CHECK-BASIC-NL: no_lbr cycles -PERF2BOLT: 0 [unknown] 7f36d18d60c0 1 main 53c 0 2 -PERF2BOLT: 1 main 451 1 SolveCubic 0 0 2 -PERF2BOLT: 1 main 490 0 [unknown] 4005f0 0 1 -PERF2BOLT: 1 main 537 0 [unknown] 400610 0 1 -PERF2BOLT: 1 usqrt 30 1 usqrt 32 0 22 -PERF2BOLT: 1 usqrt 30 1 usqrt 39 4 33 -PERF2BOLT: 1 usqrt 35 1 usqrt 39 0 22 -PERF2BOLT: 1 usqrt 3d 1 usqrt 10 0 58 -PERF2BOLT: 1 usqrt 3d 1 usqrt 3f 0 22 -PERF2BOLT: 1 usqrt a 1 usqrt 10 0 22 +PERF2BOLT: 1 frame_dummy/1 1e 1 frame_dummy/1 0 0 1 +PERF2BOLT-NEXT: 1 main 451 1 SolveCubic 0 0 2 +PERF2BOLT-NEXT: 1 main 490 0 [unknown] 0 0 1 +PERF2BOLT-NEXT: 1 main 537 0 [unknown] 0 0 1 +PERF2BOLT-NEXT: 0 [unknown] 0 1 main 53c 0 2 +PERF2BOLT-NEXT: 1 usqrt a 1 usqrt 10 0 22 +PERF2BOLT-NEXT: 1 usqrt 30 1 usqrt 32 0 22 +PERF2BOLT-NEXT: 1 usqrt 30 1 usqrt 39 4 33 +PERF2BOLT-NEXT: 1 usqrt 35 1 usqrt 39 0 22 +PERF2BOLT-NEXT: 1 usqrt 3d 1 usqrt 10 0 58 +PERF2BOLT-NEXT: 1 usqrt 3d 1 usqrt 3f 0 22 NEWFORMAT: - name: 'frame_dummy/1' NEWFORMAT: fid: 3 diff --git a/bolt/test/X86/register-fragments-bolt-symbols.s b/bolt/test/X86/register-fragments-bolt-symbols.s index c9f1859c4e8a..20e7345541d9 100644 --- a/bolt/test/X86/register-fragments-bolt-symbols.s +++ b/bolt/test/X86/register-fragments-bolt-symbols.s @@ -29,6 +29,7 @@ # RUN: link_fdata %s %t.bolt %t.preagg PREAGG # PREAGG: B X:0 #chain.cold.0# 1 0 +# PREAGG: B X:0 #dummy# 1 0 # RUN: perf2bolt %t.bolt -p %t.preagg --pa -o %t.bat.fdata -w %t.bat.yaml -v=1 \ # RUN: | FileCheck %s --check-prefix=CHECK-REGISTER # RUN: FileCheck --input-file %t.bat.fdata --check-prefix=CHECK-FDATA %s @@ -44,7 +45,13 @@ # CHECK-SYMS: l F .text.cold [[#]] chain.cold.0 # CHECK-SYMS: l F .text [[#]] chain # CHECK-SYMS: l df *ABS* [[#]] bolt-pseudo.o +# CHECK-SYMS: l F .text.cold [[#]] dummy.cold.0 +# CHECK-SYMS: l F .text.cold.1 [[#]] dummy.cold.1 +# CHECK-SYMS: l F .text.cold.2 [[#]] dummy.cold.2 +# CHECK-REGISTER: BOLT-INFO: marking dummy.cold.0/1(*2) as a fragment of dummy +# CHECK-REGISTER: BOLT-INFO: marking dummy.cold.1/1(*2) as a fragment of dummy +# CHECK-REGISTER: BOLT-INFO: marking dummy.cold.2/1(*2) as a fragment of dummy # CHECK-REGISTER: BOLT-INFO: marking chain.cold.0/1(*2) as a fragment of chain/2(*2) # CHECK-FDATA: 0 [unknown] 0 1 chain/chain.s/2 10 0 1 diff --git a/bolt/test/X86/shrinkwrapping.test b/bolt/test/X86/shrinkwrapping.test index 8581d7e0c0f7..521b4561b3ba 100644 --- a/bolt/test/X86/shrinkwrapping.test +++ b/bolt/test/X86/shrinkwrapping.test @@ -8,6 +8,7 @@ REQUIRES: shell RUN: %clangxx %cxxflags -no-pie %S/Inputs/exc4sw.S -o %t.exe -Wl,-q RUN: llvm-bolt %t.exe -o %t --relocs --frame-opt=all \ +RUN: --print-only=main --print-cfg \ RUN: --data=%p/Inputs/exc4sw.fdata --reorder-blocks=cache 2>&1 | \ RUN: FileCheck %s --check-prefix=CHECK-BOLT @@ -19,6 +20,7 @@ RUN: llvm-objdump --dwarf=frames %t | grep -A20 -e \ RUN: `llvm-nm --numeric-sort %t | grep main | tail -n 1 | cut -f1 -d' ' | \ RUN: tail -c9` 2>&1 | FileCheck %s --check-prefix=CHECK-OUTPUT +CHECK-BOLT: Extern Entry Count: 100 CHECK-BOLT: Shrink wrapping moved 2 spills inserting load/stores and 0 spills inserting push/pops CHECK-INPUT: DW_CFA_advance_loc: 2 diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s new file mode 100644 index 000000000000..717bf40df3d0 --- /dev/null +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s @@ -0,0 +1,812 @@ +// RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe +// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s +// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s + +// The detection of compiler-generated explicit pointer checks is tested in +// gs-pauth-address-checks.s, for that reason only test here "dummy-load" and +// "high-bits-notbi" checkers, as the shortest examples of checkers that are +// detected per-instruction and per-BB. + +// PACRET-NOT: authentication oracle found in function + + .text + + .type sym,@function +sym: + ret + .size sym, .-sym + + .globl callee + .type callee,@function +callee: + ret + .size callee, .-callee + + .globl good_ret + .type good_ret,@function +good_ret: +// CHECK-NOT: good_ret + autia x0, x1 + ret x0 + .size good_ret, .-good_ret + + .globl good_call + .type good_call,@function +good_call: +// CHECK-NOT: good_call + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + autia x0, x1 + blr x0 + + ldp x29, x30, [sp], #16 + autiasp + ret + .size good_call, .-good_call + + .globl good_branch + .type good_branch,@function +good_branch: +// CHECK-NOT: good_branch + autia x0, x1 + br x0 + .size good_branch, .-good_branch + + .globl good_load_other_reg + .type good_load_other_reg,@function +good_load_other_reg: +// CHECK-NOT: good_load_other_reg + autia x0, x1 + ldr x2, [x0] + ret + .size good_load_other_reg, .-good_load_other_reg + + .globl good_load_same_reg + .type good_load_same_reg,@function +good_load_same_reg: +// CHECK-NOT: good_load_same_reg + autia x0, x1 + ldr x0, [x0] + ret + .size good_load_same_reg, .-good_load_same_reg + + .globl good_explicit_check + .type good_explicit_check,@function +good_explicit_check: +// CHECK-NOT: good_explicit_check + autia x0, x1 + eor x16, x0, x0, lsl #1 + tbz x16, #62, 1f + brk 0x1234 +1: + ret + .size good_explicit_check, .-good_explicit_check + + .globl bad_unchecked + .type bad_unchecked,@function +bad_unchecked: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + autia x0, x1 + ret + .size bad_unchecked, .-bad_unchecked + + .globl bad_leaked_to_subroutine + .type bad_leaked_to_subroutine,@function +bad_leaked_to_subroutine: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl callee +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: paciasp +// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp +// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: {{[0-9a-f]+}}: bl callee +// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0] +// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: {{[0-9a-f]+}}: ret + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + autia x0, x1 + bl callee + ldr x2, [x0] + + ldp x29, x30, [sp], #16 + autiasp + ret + .size bad_leaked_to_subroutine, .-bad_leaked_to_subroutine + + .globl bad_unknown_usage_read + .type bad_unknown_usage_read,@function +bad_unknown_usage_read: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul x3, x0, x1 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: {{[0-9a-f]+}}: mul x3, x0, x1 +// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0] +// CHECK-NEXT: {{[0-9a-f]+}}: ret + autia x0, x1 + // Registers are not accessible to an attacker under Pointer + // Authentication threat model, until spilled to memory. + // Thus, reporting the below MUL instruction is a false positive, since + // the next LDR instruction prevents any possible spilling of x3 unless + // the authentication succeeded. Though, rejecting anything except for + // a closed list of instruction types is the intended behavior of the + // analysis, so this false positive is by design. + mul x3, x0, x1 + ldr x2, [x0] + ret + .size bad_unknown_usage_read, .-bad_unknown_usage_read + + .globl bad_store_to_memory_and_wait + .type bad_store_to_memory_and_wait,@function +bad_store_to_memory_and_wait: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_store_to_memory_and_wait, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: str x0, [x3] + autia x0, x1 + cbz x3, 2f + str x0, [x3] +1: + // The thread performs a time-consuming computation while the result of + // authentication is accessible in memory. + nop +2: + ldr x2, [x0] + ret + .size bad_store_to_memory_and_wait, .-bad_store_to_memory_and_wait + +// FIXME: Known false negative: if no return instruction is reachable from a +// program point (this probably implies an infinite loop), such +// instruction cannot be detected as an authentication oracle. + .globl bad_store_to_memory_and_hang + .type bad_store_to_memory_and_hang,@function +bad_store_to_memory_and_hang: +// CHECK-NOT: bad_store_to_memory_and_hang + autia x0, x1 + cbz x3, 2f + str x0, [x3] +1: + // The thread loops indefinitely while the result of authentication + // is accessible in memory. + b 1b +2: + ldr x2, [x0] + ret + .size bad_store_to_memory_and_hang, .-bad_store_to_memory_and_hang + + .globl bad_unknown_usage_subreg_read + .type bad_unknown_usage_subreg_read,@function +bad_unknown_usage_subreg_read: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul w3, w0, w1 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: {{[0-9a-f]+}}: mul w3, w0, w1 +// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0] +// CHECK-NEXT: {{[0-9a-f]+}}: ret + autia x0, x1 + mul w3, w0, w1 + ldr x2, [x0] + ret + .size bad_unknown_usage_subreg_read, .-bad_unknown_usage_subreg_read + + .globl bad_unknown_usage_update + .type bad_unknown_usage_update,@function +bad_unknown_usage_update: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16 +// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0] +// CHECK-NEXT: {{[0-9a-f]+}}: ret + autia x0, x1 + movk x0, #42, lsl #16 // does not overwrite x0 completely + ldr x2, [x0] + ret + .size bad_unknown_usage_update, .-bad_unknown_usage_update + + .globl good_overwrite_with_constant + .type good_overwrite_with_constant,@function +good_overwrite_with_constant: +// CHECK-NOT: good_overwrite_with_constant + autia x0, x1 + mov x0, #42 + ret + .size good_overwrite_with_constant, .-good_overwrite_with_constant + +// Overwriting sensitive data by instructions with unmodelled side-effects is +// explicitly rejected, even though this particular MRS is safe. + .globl bad_overwrite_with_side_effects + .type bad_overwrite_with_side_effects,@function +bad_overwrite_with_side_effects: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_overwrite_with_side_effects, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + autia x0, x1 + mrs x0, CTR_EL0 + ret + .size bad_overwrite_with_side_effects, .-bad_overwrite_with_side_effects + +// Here the new value written by MUL to x0 is completely unrelated to the result +// of authentication, so this is a false positive. +// FIXME: Can/should we generalize overwriting by constant to handle such cases? + .globl good_unknown_overwrite + .type good_unknown_overwrite,@function +good_unknown_overwrite: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_unknown_overwrite, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + autia x0, x1 + mul x0, x1, x2 + ret + .size good_unknown_overwrite, .-good_unknown_overwrite + +// This is a false positive: when a general-purpose register is written to as +// a 32-bit register, its top 32 bits are zeroed, but according to LLVM +// representation, the instruction only overwrites the Wn register. + .globl good_wreg_overwrite + .type good_wreg_overwrite,@function +good_wreg_overwrite: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_wreg_overwrite, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 + autia x0, x1 + mov w0, #42 + ret + .size good_wreg_overwrite, .-good_wreg_overwrite + + .globl good_address_arith + .type good_address_arith,@function +good_address_arith: +// CHECK-NOT: good_address_arith + autia x0, x1 + + add x1, x0, #8 + sub x2, x1, #16 + mov x3, x2 + + ldr x4, [x3] + mov x0, #0 + mov x1, #0 + mov x2, #0 + + ret + .size good_address_arith, .-good_address_arith + + .globl good_ret_multi_bb + .type good_ret_multi_bb,@function +good_ret_multi_bb: +// CHECK-NOT: good_ret_multi_bb + autia x0, x1 + cbz x1, 1f + nop +1: + ret x0 + .size good_ret_multi_bb, .-good_ret_multi_bb + + .globl good_call_multi_bb + .type good_call_multi_bb,@function +good_call_multi_bb: +// CHECK-NOT: good_call_multi_bb + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + autia x0, x1 + cbz x1, 1f + nop +1: + blr x0 + cbz x1, 2f + nop +2: + ldp x29, x30, [sp], #16 + autiasp + ret + .size good_call_multi_bb, .-good_call_multi_bb + + .globl good_branch_multi_bb + .type good_branch_multi_bb,@function +good_branch_multi_bb: +// CHECK-NOT: good_branch_multi_bb + autia x0, x1 + cbz x1, 1f + nop +1: + br x0 + .size good_branch_multi_bb, .-good_branch_multi_bb + + .globl good_load_other_reg_multi_bb + .type good_load_other_reg_multi_bb,@function +good_load_other_reg_multi_bb: +// CHECK-NOT: good_load_other_reg_multi_bb + autia x0, x1 + cbz x1, 1f + nop +1: + ldr x2, [x0] + cbz x1, 2f + nop +2: + ret + .size good_load_other_reg_multi_bb, .-good_load_other_reg_multi_bb + + .globl good_load_same_reg_multi_bb + .type good_load_same_reg_multi_bb,@function +good_load_same_reg_multi_bb: +// CHECK-NOT: good_load_same_reg_multi_bb + autia x0, x1 + cbz x1, 1f + nop +1: + ldr x0, [x0] + cbz x1, 2f + nop +2: + ret + .size good_load_same_reg_multi_bb, .-good_load_same_reg_multi_bb + + .globl good_explicit_check_multi_bb + .type good_explicit_check_multi_bb,@function +good_explicit_check_multi_bb: +// CHECK-NOT: good_explicit_check_multi_bb + autia x0, x1 + cbz x1, 1f + nop +1: + eor x16, x0, x0, lsl #1 + tbz x16, #62, 2f + brk 0x1234 +2: + cbz x1, 3f + nop +3: + ret + .size good_explicit_check_multi_bb, .-good_explicit_check_multi_bb + + .globl bad_unchecked_multi_bb + .type bad_unchecked_multi_bb,@function +bad_unchecked_multi_bb: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + autia x0, x1 + cbz x1, 1f + ldr x2, [x0] +1: + ret + .size bad_unchecked_multi_bb, .-bad_unchecked_multi_bb + + .globl bad_leaked_to_subroutine_multi_bb + .type bad_leaked_to_subroutine_multi_bb,@function +bad_leaked_to_subroutine_multi_bb: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl callee + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + autia x0, x1 + cbz x1, 1f + ldr x2, [x0] +1: + bl callee + ldr x2, [x0] + + ldp x29, x30, [sp], #16 + autiasp + ret + .size bad_leaked_to_subroutine_multi_bb, .-bad_leaked_to_subroutine_multi_bb + + .globl bad_unknown_usage_read_multi_bb + .type bad_unknown_usage_read_multi_bb,@function +bad_unknown_usage_read_multi_bb: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul x3, x0, x1 + autia x0, x1 + cbz x3, 1f + mul x3, x0, x1 +1: + ldr x2, [x0] + ret + .size bad_unknown_usage_read_multi_bb, .-bad_unknown_usage_read_multi_bb + + .globl bad_unknown_usage_subreg_read_multi_bb + .type bad_unknown_usage_subreg_read_multi_bb,@function +bad_unknown_usage_subreg_read_multi_bb: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul w3, w0, w1 + autia x0, x1 + cbz x3, 1f + mul w3, w0, w1 +1: + ldr x2, [x0] + ret + .size bad_unknown_usage_subreg_read_multi_bb, .-bad_unknown_usage_subreg_read_multi_bb + + .globl bad_unknown_usage_update_multi_bb + .type bad_unknown_usage_update_multi_bb,@function +bad_unknown_usage_update_multi_bb: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16 + autia x0, x1 + cbz x3, 1f + movk x0, #42, lsl #16 // does not overwrite x0 completely +1: + ldr x2, [x0] + ret + .size bad_unknown_usage_update_multi_bb, .-bad_unknown_usage_update_multi_bb + + .globl good_overwrite_with_constant_multi_bb + .type good_overwrite_with_constant_multi_bb,@function +good_overwrite_with_constant_multi_bb: +// CHECK-NOT: good_overwrite_with_constant_multi_bb + autia x0, x1 + cbz x3, 1f +1: + mov x0, #42 + ret + .size good_overwrite_with_constant_multi_bb, .-good_overwrite_with_constant_multi_bb + + .globl good_address_arith_multi_bb + .type good_address_arith_multi_bb,@function +good_address_arith_multi_bb: +// CHECK-NOT: good_address_arith_multi_bb + autia x0, x1 + cbz x3, 1f + + add x1, x0, #8 + sub x2, x1, #16 + mov x0, x2 + + mov x1, #0 + mov x2, #0 +1: + ldr x3, [x0] + ret + .size good_address_arith_multi_bb, .-good_address_arith_multi_bb + +// FIXME: Most *_nocfg test cases contain paciasp+autiasp instructions even if +// LR is not spilled - this is a workaround for RET instructions being +// reported as non-protected, because LR state is reset at every label. + + .globl good_ret_nocfg + .type good_ret_nocfg,@function +good_ret_nocfg: +// CHECK-NOT: good_ret_nocfg + adr x2, 1f + br x2 +1: + autia x0, x1 + + ret x0 + .size good_ret_nocfg, .-good_ret_nocfg + + .globl good_call_nocfg + .type good_call_nocfg,@function +good_call_nocfg: +// CHECK-NOT: good_call_nocfg + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + adr x2, 1f + br x2 +1: + autia x0, x1 + blr x0 + + ldp x29, x30, [sp], #16 + autiasp + ret + .size good_call_nocfg, .-good_call_nocfg + + .globl good_branch_nocfg + .type good_branch_nocfg,@function +good_branch_nocfg: +// CHECK-NOT: good_branch_nocfg + adr x2, 1f + br x2 +1: + autia x0, x1 + br x0 + .size good_branch_nocfg, .-good_branch_nocfg + + .globl good_load_other_reg_nocfg + .type good_load_other_reg_nocfg,@function +good_load_other_reg_nocfg: +// CHECK-NOT: good_load_other_reg_nocfg + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + ldr x2, [x0] + + autiasp + ret + .size good_load_other_reg_nocfg, .-good_load_other_reg_nocfg + + .globl good_load_same_reg_nocfg + .type good_load_same_reg_nocfg,@function +good_load_same_reg_nocfg: +// CHECK-NOT: good_load_same_reg_nocfg + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + ldr x0, [x0] + + autiasp + ret + .size good_load_same_reg_nocfg, .-good_load_same_reg_nocfg + +// FIXME: Multi-instruction checker sequences are not supported without CFG. + + .globl bad_unchecked_nocfg + .type bad_unchecked_nocfg,@function +bad_unchecked_nocfg: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + + autiasp + ret + .size bad_unchecked_nocfg, .-bad_unchecked_nocfg + + .globl bad_leaked_to_subroutine_nocfg + .type bad_leaked_to_subroutine_nocfg,@function +bad_leaked_to_subroutine_nocfg: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl callee # Offset: 24 + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + adr x2, 1f + br x2 +1: + autia x0, x1 + bl callee + ldr x2, [x0] + + ldp x29, x30, [sp], #16 + autiasp + ret + .size bad_leaked_to_subroutine_nocfg, .-bad_leaked_to_subroutine_nocfg + + .globl bad_unknown_usage_read_nocfg + .type bad_unknown_usage_read_nocfg,@function +bad_unknown_usage_read_nocfg: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul x3, x0, x1 + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + mul x3, x0, x1 + ldr x2, [x0] + + autiasp + ret + .size bad_unknown_usage_read_nocfg, .-bad_unknown_usage_read_nocfg + + .globl bad_unknown_usage_subreg_read_nocfg + .type bad_unknown_usage_subreg_read_nocfg,@function +bad_unknown_usage_subreg_read_nocfg: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul w3, w0, w1 + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + mul w3, w0, w1 + ldr x2, [x0] + + autiasp + ret + .size bad_unknown_usage_subreg_read_nocfg, .-bad_unknown_usage_subreg_read_nocfg + + .globl bad_unknown_usage_update_nocfg + .type bad_unknown_usage_update_nocfg,@function +bad_unknown_usage_update_nocfg: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16 + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + movk x0, #42, lsl #16 // does not overwrite x0 completely + ldr x2, [x0] + + autiasp + ret + .size bad_unknown_usage_update_nocfg, .-bad_unknown_usage_update_nocfg + + .globl good_overwrite_with_constant_nocfg + .type good_overwrite_with_constant_nocfg,@function +good_overwrite_with_constant_nocfg: +// CHECK-NOT: good_overwrite_with_constant_nocfg + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + mov x0, #42 + + autiasp + ret + .size good_overwrite_with_constant_nocfg, .-good_overwrite_with_constant_nocfg + + .globl good_address_arith_nocfg + .type good_address_arith_nocfg,@function +good_address_arith_nocfg: +// CHECK-NOT: good_address_arith_nocfg + paciasp + adr x2, 1f + br x2 +1: + autia x0, x1 + add x1, x0, #8 + sub x2, x1, #16 + mov x3, x2 + + ldr x4, [x3] + mov x0, #0 + mov x1, #0 + mov x2, #0 + + autiasp + ret + .size good_address_arith_nocfg, .-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 + 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 + // false-positive here. + autia x0, x1 + eor x16, x0, x0, lsl #1 + tbz x16, #62, 1f + brk 0x1234 +1: + ldr x4, [x2] // Right before this instruction X2 is checked - this + // should be propagated to the basic block ending with + // TBZ instruction above. + ret + .size good_explicit_check_unrelated_reg, .-good_explicit_check_unrelated_reg + +// The last BB (in layout order) is processed first by the data-flow analysis. +// Its initial state is usually filled in a special way (because it ends with +// `ret` instruction), and then affects the state propagated to the other BBs +// Thus, the case of the last instruction in a function being a jump somewhere +// in the middle is special. + + .globl good_no_ret_from_last_bb + .type good_no_ret_from_last_bb,@function +good_no_ret_from_last_bb: +// CHECK-NOT: good_no_ret_from_last_bb + paciasp + autiasp // authenticates LR + b 2f +1: + ret +2: + b 1b // LR is dereferenced by `ret`, which is executed next + .size good_no_ret_from_last_bb, .-good_no_ret_from_last_bb + + .globl bad_no_ret_from_last_bb + .type bad_no_ret_from_last_bb,@function +bad_no_ret_from_last_bb: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_no_ret_from_last_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 0 instructions that leak the affected registers are: + paciasp + autiasp // authenticates LR + b 2f +1: + ret x0 +2: + b 1b // X0 (but not LR) is dereferenced by `ret x0` + .size bad_no_ret_from_last_bb, .-bad_no_ret_from_last_bb + +// Test that combined auth+something instructions are not reported as +// authentication oracles. + + .globl inst_retaa + .type inst_retaa,@function +inst_retaa: +// CHECK-NOT: inst_retaa + paciasp + retaa + .size inst_retaa, .-inst_retaa + + .globl inst_blraa + .type inst_blraa,@function +inst_blraa: +// CHECK-NOT: inst_blraa + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + blraa x0, x1 + + ldp x29, x30, [sp], #16 + retaa + .size inst_blraa, .-inst_blraa + + .globl inst_braa + .type inst_braa,@function +inst_braa: +// CHECK-NOT: inst_braa + braa x0, x1 + .size inst_braa, .-inst_braa + + .globl inst_ldraa_no_wb + .type inst_ldraa_no_wb,@function +inst_ldraa_no_wb: +// CHECK-NOT: inst_ldraa_no_wb + ldraa x1, [x0] + ret + .size inst_ldraa_no_wb, .-inst_ldraa_no_wb + + .globl inst_ldraa_wb + .type inst_ldraa_wb,@function +inst_ldraa_wb: +// CHECK-NOT: inst_ldraa_wb + ldraa x1, [x0]! + ret + .size inst_ldraa_wb, .-inst_ldraa_wb + + .globl main + .type main,@function +main: + mov x0, 0 + ret + .size main, .-main diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s index 82494d834a15..fbb96a63d41e 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-debug-output.s @@ -113,7 +113,7 @@ simple: // CHECK-EMPTY: // PAUTH-NEXT: Found sign inst: 00000000: paciasp # DataflowSrcSafetyAnalysis: src-state // PAUTH-NEXT: Signed reg: LR -// PAUTH-NEXT: TrustedRegs: LR W30 W30_HI +// PAUTH-NEXT: TrustedRegs: LR W30 W30_HI{{[ \t]*$}} // PAUTH-NEXT: Found call inst: 00000000: blr x0 # DataflowSrcSafetyAnalysis: src-state // PAUTH-NEXT: Call destination reg: X0 // PAUTH-NEXT: SafeToDerefRegs: W0 X0 W0_HI{{[ \t]*$}} @@ -220,10 +220,10 @@ nocfg: // CHECK-EMPTY: // PAUTH-NEXT: Found call inst: 00000000: br x0 # UNKNOWN CONTROL FLOW # Offset: 4 # CFGUnawareSrcSafetyAnalysis: src-state // PAUTH-NEXT: Call destination reg: X0 -// PAUTH-NEXT: SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI +// PAUTH-NEXT: SafeToDerefRegs: LR W0 W30 X0 W0_HI W30_HI{{[ \t]*$}} // CHECK-NEXT: Found RET inst: 00000000: ret # Offset: 8 # CFGUnawareSrcSafetyAnalysis: src-state // CHECK-NEXT: RetReg: LR -// CHECK-NEXT: SafeToDerefRegs: +// CHECK-NEXT: SafeToDerefRegs:{{[ \t]*$}} // CHECK-EMPTY: // CHECK-NEXT: Running detailed src register safety analysis... // CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( adr x0, __ENTRY_nocfg@0x[[ENTRY_ADDR]], src-state) @@ -251,6 +251,116 @@ nocfg: // CHECK-EMPTY: // CHECK-NEXT: Attaching clobbering info to: 00000000: ret # Offset: 8 # CFGUnawareSrcSafetyAnalysis: src-state + .globl auth_oracle + .type auth_oracle,@function +auth_oracle: + autia x0, x1 + ret + .size auth_oracle, .-auth_oracle + +// CHECK-LABEL:Analyzing function auth_oracle, AllocatorId = 1 +// CHECK-NEXT: Binary Function "auth_oracle" { +// CHECK-NEXT: Number : 4 +// CHECK-NEXT: State : CFG constructed +// ... +// CHECK: BB Layout : [[BB0:[0-9a-zA-Z.]+]] +// CHECK-NEXT: } +// CHECK-NEXT: [[BB0]] (2 instructions, align : 1) +// CHECK-NEXT: Entry Point +// CHECK-NEXT: 00000000: autia x0, x1 +// CHECK-NEXT: 00000004: ret +// CHECK-EMPTY: +// CHECK-NEXT: DWARF CFI Instructions: +// CHECK-NEXT: +// CHECK-NEXT: End of Function "auth_oracle" +// CHECK-EMPTY: +// CHECK-NEXT: Running src register safety analysis... +// ... +// CHECK: After src register safety analysis: +// CHECK-NEXT: Binary Function "auth_oracle" { +// ... +// CHECK: End of Function "auth_oracle" +// ... +// PAUTH: Running dst register safety analysis... +// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( ret x30, dst-state) +// PAUTH-NEXT: .. result: (dst-state) +// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state) +// PAUTH-NEXT: .. result: (dst-state) +// PAUTH-NEXT: After dst register safety analysis: +// PAUTH-NEXT: Binary Function "auth_oracle" { +// PAUTH-NEXT: Number : 4 +// PAUTH-NEXT: State : CFG constructed +// ... +// PAUTH: BB Layout : [[BB0]] +// PAUTH-NEXT: } +// PAUTH-NEXT: [[BB0]] (2 instructions, align : 1) +// PAUTH-NEXT: Entry Point +// PAUTH-NEXT: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state +// PAUTH-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state +// PAUTH-EMPTY: +// PAUTH-NEXT: DWARF CFI Instructions: +// PAUTH-NEXT: +// PAUTH-NEXT: End of Function "auth_oracle" +// PAUTH-EMPTY: +// PAUTH-NEXT: Found auth inst: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state +// PAUTH-NEXT: Authenticated reg: X0 +// PAUTH-NEXT: safe output registers: LR W30 W30_HI{{[ \t]*$}} +// PAUTH-EMPTY: +// PAUTH-NEXT: Running detailed dst register safety analysis... +// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( ret x30, dst-state) +// PAUTH-NEXT: .. result: (dst-state) +// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state) +// PAUTH-NEXT: .. result: (dst-state) +// PAUTH-NEXT: After detailed dst register safety analysis: +// PAUTH-NEXT: Binary Function "auth_oracle" { +// PAUTH-NEXT: Number : 4 +// PAUTH-NEXT: State : CFG constructed +// ... +// PAUTH: BB Layout : [[BB0]] +// PAUTH-NEXT: } +// PAUTH-NEXT: [[BB0]] (2 instructions, align : 1) +// PAUTH-NEXT: Entry Point +// PAUTH-NEXT: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state +// PAUTH-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state +// PAUTH-EMPTY: +// PAUTH-NEXT: DWARF CFI Instructions: +// PAUTH-NEXT: +// PAUTH-NEXT: End of Function "auth_oracle" +// PAUTH-EMPTY: +// PAUTH-NEXT: Attaching leakage info to: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state + +// Gadget scanner should not crash on CFI instructions, including when debug-printing them. +// Note that the particular debug output is not checked, but BOLT should be +// compiled with assertions enabled to support -debug-only argument. + + .globl cfi_inst_df + .type cfi_inst_df,@function +cfi_inst_df: + .cfi_startproc + sub sp, sp, #16 + .cfi_def_cfa_offset 16 + add sp, sp, #16 + .cfi_def_cfa_offset 0 + ret + .size cfi_inst_df, .-cfi_inst_df + .cfi_endproc + + .globl cfi_inst_nocfg + .type cfi_inst_nocfg,@function +cfi_inst_nocfg: + .cfi_startproc + sub sp, sp, #16 + .cfi_def_cfa_offset 16 + + adr x0, 1f + br x0 +1: + add sp, sp, #16 + .cfi_def_cfa_offset 0 + ret + .size cfi_inst_nocfg, .-cfi_inst_nocfg + .cfi_endproc + // CHECK-LABEL:Analyzing function main, AllocatorId = 1 .globl main .type main,@function diff --git a/bolt/test/link_fdata.py b/bolt/test/link_fdata.py index 5a9752068bb9..898dce8e3fb5 100755 --- a/bolt/test/link_fdata.py +++ b/bolt/test/link_fdata.py @@ -36,9 +36,9 @@ fdata_pat = re.compile(r"([01].*) (?P\d+) (?P\d+)") # Pre-aggregated profile: -# {T|S|E|B|F|f} [] [] [] +# {T|R|S|E|B|F|f|r} [] [] [] # : [:] -preagg_pat = re.compile(r"(?P[TSBFf]) (?P.*)") +preagg_pat = re.compile(r"(?P[TRSBFfr]) (?P.*)") # No-LBR profile: # diff --git a/bolt/test/lit.local.cfg b/bolt/test/lit.local.cfg index d5a6849b27a7..8a61d11f5825 100644 --- a/bolt/test/lit.local.cfg +++ b/bolt/test/lit.local.cfg @@ -1,6 +1,11 @@ -host_linux_triple = config.target_triple.split("-")[0] + "-unknown-linux-gnu" +host_triple = config.target_triple + +# Force triple on non-linux hosts to get ELF binaries on all platforms. +if not "linux" in host_triple: + host_triple = host_triple.split("-")[0] + "-unknown-linux-gnu" + common_linker_flags = "-fuse-ld=lld -Wl,--unresolved-symbols=ignore-all -Wl,--build-id=none -pie" -flags = f"--target={host_linux_triple} -fPIE {common_linker_flags}" +flags = f"--target={host_triple} -fPIE {common_linker_flags}" config.substitutions.insert(0, ("%cflags", f"%cflags {flags}")) config.substitutions.insert(0, ("%cxxflags", f"%cxxflags {flags}")) diff --git a/bolt/test/perf2bolt/AArch64/perf2bolt-spe.test b/bolt/test/perf2bolt/AArch64/perf2bolt-spe.test new file mode 100644 index 000000000000..91f5c857fbab --- /dev/null +++ b/bolt/test/perf2bolt/AArch64/perf2bolt-spe.test @@ -0,0 +1,12 @@ +## Check that Arm SPE mode is available on AArch64. + +REQUIRES: system-linux,perf,target=aarch64{{.*}} + +RUN: %clang %cflags %p/../../Inputs/asm_foo.s %p/../../Inputs/asm_main.c -o %t.exe + +RUN: perf record -e cycles -q -o %t.perf.data -- %t.exe 2> /dev/null + +RUN: (perf2bolt -p %t.perf.data -o %t.perf.boltdata --spe %t.exe 2> /dev/null; exit 0) | FileCheck %s --check-prefix=CHECK-SPE-LBR + +CHECK-SPE-LBR: PERF2BOLT: parse SPE branch events in LBR-format + diff --git a/bolt/test/perf2bolt/X86/perf2bolt-spe.test b/bolt/test/perf2bolt/X86/perf2bolt-spe.test new file mode 100644 index 000000000000..101bd3789a18 --- /dev/null +++ b/bolt/test/perf2bolt/X86/perf2bolt-spe.test @@ -0,0 +1,9 @@ +## Check that Arm SPE mode is unavailable on X86. + +REQUIRES: system-linux,x86_64-linux + +RUN: %clang %cflags %p/../../Inputs/asm_foo.s %p/../../Inputs/asm_main.c -o %t.exe +RUN: touch %t.empty.perf.data +RUN: not perf2bolt -p %t.empty.perf.data -o %t.perf.boltdata --spe --pa %t.exe 2>&1 | FileCheck %s + +CHECK: perf2bolt{{.*}} -spe is available only on AArch64. diff --git a/bolt/tools/driver/llvm-bolt.cpp b/bolt/tools/driver/llvm-bolt.cpp index b9836c2397b6..cf1b31f8c0c6 100644 --- a/bolt/tools/driver/llvm-bolt.cpp +++ b/bolt/tools/driver/llvm-bolt.cpp @@ -237,6 +237,13 @@ int main(int argc, char **argv) { if (Error E = RIOrErr.takeError()) report_error(opts::InputFilename, std::move(E)); RewriteInstance &RI = *RIOrErr.get(); + + if (opts::AggregateOnly && !RI.getBinaryContext().isAArch64() && + opts::ArmSPE) { + errs() << ToolName << ": -spe is available only on AArch64.\n"; + exit(1); + } + if (!opts::PerfData.empty()) { if (!opts::AggregateOnly) { errs() << ToolName diff --git a/bolt/unittests/Profile/CMakeLists.txt b/bolt/unittests/Profile/CMakeLists.txt index e0aa0926b49c..ce01c6c4b949 100644 --- a/bolt/unittests/Profile/CMakeLists.txt +++ b/bolt/unittests/Profile/CMakeLists.txt @@ -1,11 +1,25 @@ +set(LLVM_LINK_COMPONENTS + DebugInfoDWARF + Object + ${LLVM_TARGETS_TO_BUILD} + ) + add_bolt_unittest(ProfileTests DataAggregator.cpp + PerfSpeEvents.cpp DISABLE_LLVM_LINK_LLVM_DYLIB ) target_link_libraries(ProfileTests PRIVATE + LLVMBOLTCore LLVMBOLTProfile + LLVMTargetParser + LLVMTestingSupport ) +foreach (tgt ${BOLT_TARGETS_TO_BUILD}) + string(TOUPPER "${tgt}" upper) + target_compile_definitions(ProfileTests PRIVATE "${upper}_AVAILABLE") +endforeach() diff --git a/bolt/unittests/Profile/PerfSpeEvents.cpp b/bolt/unittests/Profile/PerfSpeEvents.cpp new file mode 100644 index 000000000000..3e3e05395246 --- /dev/null +++ b/bolt/unittests/Profile/PerfSpeEvents.cpp @@ -0,0 +1,164 @@ +//===- bolt/unittests/Profile/PerfSpeEvents.cpp ---------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifdef AARCH64_AVAILABLE + +#include "bolt/Core/BinaryContext.h" +#include "bolt/Profile/DataAggregator.h" +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/TargetSelect.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::bolt; +using namespace llvm::object; +using namespace llvm::ELF; + +namespace opts { +extern cl::opt ReadPerfEvents; +extern cl::opt ArmSPE; +} // namespace opts + +namespace llvm { +namespace bolt { + +/// Perform checks on perf SPE branch events. +struct PerfSpeEventsTestHelper : public testing::Test { + void SetUp() override { + initalizeLLVM(); + prepareElf(); + initializeBOLT(); + } + +protected: + using Trace = DataAggregator::Trace; + using TakenBranchInfo = DataAggregator::TakenBranchInfo; + + void initalizeLLVM() { + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + llvm::InitializeAllDisassemblers(); + llvm::InitializeAllTargets(); + llvm::InitializeAllAsmPrinters(); + } + + void prepareElf() { + memcpy(ElfBuf, "\177ELF", 4); + ELF64LE::Ehdr *EHdr = reinterpret_cast(ElfBuf); + EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64; + EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB; + EHdr->e_machine = llvm::ELF::EM_AARCH64; + MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF"); + ObjFile = cantFail(ObjectFile::createObjectFile(Source)); + } + + void initializeBOLT() { + Relocation::Arch = ObjFile->makeTriple().getArch(); + BC = cantFail(BinaryContext::createBinaryContext( + ObjFile->makeTriple(), std::make_shared(), + ObjFile->getFileName(), nullptr, /*IsPIC*/ false, + DWARFContext::create(*ObjFile.get()), {llvm::outs(), llvm::errs()})); + ASSERT_FALSE(!BC); + } + + char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {}; + std::unique_ptr ObjFile; + std::unique_ptr BC; + + /// Helper function to export lists to show the mismatch. + void reportBrStackEventMismatch( + const std::vector> &Traces, + const std::vector> &ExpectedSamples) { + llvm::errs() << "Traces items: \n"; + for (const auto &[Trace, BI] : Traces) + llvm::errs() << "{" << Trace.Branch << ", " << Trace.From << "," + << Trace.To << ", " << BI.TakenCount << ", " + << BI.MispredCount << "}" << "\n"; + + llvm::errs() << "Expected items: \n"; + for (const auto &[Trace, BI] : ExpectedSamples) + llvm::errs() << "{" << Trace.Branch << ", " << Trace.From << ", " + << Trace.To << ", " << BI.TakenCount << ", " + << BI.MispredCount << "}" << "\n"; + } + + /// Parse and check SPE brstack as LBR. + void parseAndCheckBrstackEvents( + uint64_t PID, + const std::vector> &ExpectedSamples) { + DataAggregator DA(""); + DA.ParsingBuf = opts::ReadPerfEvents; + DA.BC = BC.get(); + DataAggregator::MMapInfo MMap; + DA.BinaryMMapInfo.insert(std::make_pair(PID, MMap)); + + DA.parseBranchEvents(); + + EXPECT_EQ(DA.Traces.size(), ExpectedSamples.size()); + if (DA.Traces.size() != ExpectedSamples.size()) + reportBrStackEventMismatch(DA.Traces, ExpectedSamples); + + const auto TracesBegin = DA.Traces.begin(); + const auto TracesEnd = DA.Traces.end(); + for (const auto &BI : ExpectedSamples) { + auto it = find_if(TracesBegin, TracesEnd, + [&BI](const auto &Tr) { return Tr.first == BI.first; }); + + EXPECT_NE(it, TracesEnd); + EXPECT_EQ(it->second.MispredCount, BI.second.MispredCount); + EXPECT_EQ(it->second.TakenCount, BI.second.TakenCount); + } + } +}; + +} // namespace bolt +} // namespace llvm + +TEST_F(PerfSpeEventsTestHelper, SpeBranchesWithBrstack) { + // Check perf input with SPE branch events as brstack format. + // Example collection command: + // ``` + // perf record -e 'arm_spe_0/branch_filter=1/u' -- BINARY + // ``` + // How Bolt extracts the branch events: + // ``` + // perf script -F pid,brstack --itrace=bl + // ``` + + opts::ArmSPE = true; + opts::ReadPerfEvents = " 1234 0xa001/0xa002/PN/-/-/10/COND/-\n" + " 1234 0xb001/0xb002/P/-/-/4/RET/-\n" + " 1234 0xc456/0xc789/P/-/-/13/-/-\n" + " 1234 0xd123/0xd456/M/-/-/7/RET/-\n" + " 1234 0xe001/0xe002/P/-/-/14/RET/-\n" + " 1234 0xd123/0xd456/M/-/-/7/RET/-\n" + " 1234 0xf001/0xf002/MN/-/-/8/COND/-\n" + " 1234 0xc456/0xc789/M/-/-/13/-/-\n"; + + // ExpectedSamples contains the aggregated information about + // a branch {{Branch From, To}, {TakenCount, MispredCount}}. + // Consider this example trace: {{0xd123, 0xd456, Trace::BR_ONLY}, + // {2,2}}. This entry has a TakenCount = 2, as we have two samples for + // (0xd123, 0xd456) in our input. It also has MispredsCount = 2, + // as 'M' misprediction flag appears in both cases. BR_ONLY means + // the trace only contains branch data. + std::vector> ExpectedSamples = { + {{0xa001, 0xa002, Trace::BR_ONLY}, {1, 0}}, + {{0xb001, 0xb002, Trace::BR_ONLY}, {1, 0}}, + {{0xc456, 0xc789, Trace::BR_ONLY}, {2, 1}}, + {{0xd123, 0xd456, Trace::BR_ONLY}, {2, 2}}, + {{0xe001, 0xe002, Trace::BR_ONLY}, {1, 0}}, + {{0xf001, 0xf002, Trace::BR_ONLY}, {1, 1}}}; + + parseAndCheckBrstackEvents(1234, ExpectedSamples); +} + +#endif diff --git a/buildbot/configure.py b/buildbot/configure.py index 0b2a464ceb36..757046ae6fee 100644 --- a/buildbot/configure.py +++ b/buildbot/configure.py @@ -64,7 +64,7 @@ def do_configure(args, passthrough_args): sycl_enable_xpti_tracing = "ON" xpti_enable_werror = "OFF" - llvm_enable_zstd = "ON" + llvm_enable_zstd = "OFF" spirv_enable_dis = "OFF" sycl_install_device_config_file = "OFF" @@ -163,9 +163,6 @@ def do_configure(args, passthrough_args): spirv_enable_dis = "ON" sycl_install_device_config_file = "ON" - # Build compiler with zstd in CI. - llvm_enable_zstd = "FORCE_ON" - if args.enable_backends: sycl_enabled_backends += args.enable_backends diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp index 546dd0254ec0..66852931226b 100644 --- a/clang-tools-extra/clang-doc/BitcodeReader.cpp +++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp @@ -54,10 +54,8 @@ static llvm::Error decodeRecord(const Record &R, AccessSpecifier &Field, case AS_none: Field = (AccessSpecifier)R[0]; return llvm::Error::success(); - default: - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "invalid value for AccessSpecifier"); } + llvm_unreachable("invalid value for AccessSpecifier"); } static llvm::Error decodeRecord(const Record &R, TagTypeKind &Field, @@ -94,6 +92,7 @@ static llvm::Error decodeRecord(const Record &R, InfoType &Field, case InfoType::IT_default: case InfoType::IT_enum: case InfoType::IT_typedef: + case InfoType::IT_concept: Field = IT; return llvm::Error::success(); } @@ -110,6 +109,7 @@ static llvm::Error decodeRecord(const Record &R, FieldId &Field, case FieldId::F_type: case FieldId::F_child_namespace: case FieldId::F_child_record: + case FieldId::F_concept: case FieldId::F_default: Field = F; return llvm::Error::success(); @@ -315,9 +315,13 @@ static llvm::Error parseRecord(const Record &R, unsigned ID, static llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, CommentInfo *I) { + llvm::SmallString<16> KindStr; switch (ID) { case COMMENT_KIND: - return decodeRecord(R, I->Kind, Blob); + if (llvm::Error Err = decodeRecord(R, KindStr, Blob)) + return Err; + I->Kind = stringToCommentKind(KindStr); + return llvm::Error::success(); case COMMENT_TEXT: return decodeRecord(R, I->Text, Blob); case COMMENT_NAME: @@ -389,6 +393,29 @@ static llvm::Error parseRecord(const Record &R, unsigned ID, "invalid field for TemplateParamInfo"); } +static llvm::Error parseRecord(const Record &R, unsigned ID, + llvm::StringRef Blob, ConceptInfo *I) { + switch (ID) { + case CONCEPT_USR: + return decodeRecord(R, I->USR, Blob); + case CONCEPT_NAME: + return decodeRecord(R, I->Name, Blob); + case CONCEPT_IS_TYPE: + return decodeRecord(R, I->IsType, Blob); + case CONCEPT_CONSTRAINT_EXPRESSION: + return decodeRecord(R, I->ConstraintExpression, Blob); + } + llvm_unreachable("invalid field for ConceptInfo"); +} + +static llvm::Error parseRecord(const Record &R, unsigned ID, + llvm::StringRef Blob, ConstraintInfo *I) { + if (ID == CONSTRAINT_EXPRESSION) + return decodeRecord(R, I->ConstraintExpr, Blob); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for ConstraintInfo"); +} + template static llvm::Expected getCommentInfo(T I) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid type cannot contain CommentInfo"); @@ -427,6 +454,10 @@ template <> llvm::Expected getCommentInfo(CommentInfo *I) { return I->Children.back().get(); } +template <> llvm::Expected getCommentInfo(ConceptInfo *I) { + return &I->Description.emplace_back(); +} + // When readSubBlock encounters a TypeInfo sub-block, it calls addTypeInfo on // the parent block to set it. The template specializations define what to do // for each supported parent block. @@ -582,6 +613,17 @@ template <> llvm::Error addReference(RecordInfo *I, Reference &&R, FieldId F) { } } +template <> +llvm::Error addReference(ConstraintInfo *I, Reference &&R, FieldId F) { + if (F == FieldId::F_concept) { + I->ConceptRef = std::move(R); + return llvm::Error::success(); + } + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "ConstraintInfo cannot contain this Reference"); +} + template static void addChild(T I, ChildInfoType &&R) { llvm::errs() << "invalid child type for info"; @@ -598,6 +640,9 @@ template <> void addChild(NamespaceInfo *I, EnumInfo &&R) { template <> void addChild(NamespaceInfo *I, TypedefInfo &&R) { I->Children.Typedefs.emplace_back(std::move(R)); } +template <> void addChild(NamespaceInfo *I, ConceptInfo &&R) { + I->Children.Concepts.emplace_back(std::move(R)); +} // Record children: template <> void addChild(RecordInfo *I, FunctionInfo &&R) { @@ -647,6 +692,9 @@ template <> void addTemplate(RecordInfo *I, TemplateInfo &&P) { template <> void addTemplate(FunctionInfo *I, TemplateInfo &&P) { I->Template.emplace(std::move(P)); } +template <> void addTemplate(ConceptInfo *I, TemplateInfo &&P) { + I->Template = std::move(P); +} // Template specializations go only into template records. template @@ -660,6 +708,14 @@ void addTemplateSpecialization(TemplateInfo *I, I->Specialization.emplace(std::move(TSI)); } +template static void addConstraint(T I, ConstraintInfo &&C) { + llvm::errs() << "invalid container for constraint info"; + exit(1); +} +template <> void addConstraint(TemplateInfo *I, ConstraintInfo &&C) { + I->Constraints.emplace_back(std::move(C)); +} + // Read records from bitcode into a given info. template llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) { @@ -714,6 +770,8 @@ llvm::Error ClangDocBitcodeReader::readBlock(unsigned ID, T I) { } } +// TODO: Create a helper that can receive a function to reduce repetition for +// most blocks. template llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { llvm::TimeTraceScope("Reducing infos", "readSubBlock"); @@ -815,6 +873,20 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { addChild(I, std::move(TI)); return llvm::Error::success(); } + case BI_CONSTRAINT_BLOCK_ID: { + ConstraintInfo CI; + if (auto Err = readBlock(ID, &CI)) + return Err; + addConstraint(I, std::move(CI)); + return llvm::Error::success(); + } + case BI_CONCEPT_BLOCK_ID: { + ConceptInfo CI; + if (auto Err = readBlock(ID, &CI)) + return Err; + addChild(I, std::move(CI)); + return llvm::Error::success(); + } default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid subblock type"); @@ -920,6 +992,8 @@ ClangDocBitcodeReader::readBlockToInfo(unsigned ID) { return createInfo(ID); case BI_TYPEDEF_BLOCK_ID: return createInfo(ID); + case BI_CONCEPT_BLOCK_ID: + return createInfo(ID); case BI_FUNCTION_BLOCK_ID: return createInfo(ID); default: @@ -960,6 +1034,7 @@ ClangDocBitcodeReader::readBitcode() { case BI_RECORD_BLOCK_ID: case BI_ENUM_BLOCK_ID: case BI_TYPEDEF_BLOCK_ID: + case BI_CONCEPT_BLOCK_ID: case BI_FUNCTION_BLOCK_ID: { auto InfoOrErr = readBlockToInfo(ID); if (!InfoOrErr) diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp index f0a445e606bf..b7308c012786 100644 --- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp +++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp @@ -128,7 +128,9 @@ static const llvm::IndexedMap {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"}, {BI_TEMPLATE_BLOCK_ID, "TemplateBlock"}, {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, "TemplateSpecializationBlock"}, - {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"}}; + {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"}, + {BI_CONSTRAINT_BLOCK_ID, "ConstraintBlock"}, + {BI_CONCEPT_BLOCK_ID, "ConceptBlock"}}; assert(Inits.size() == BlockIdCount); for (const auto &Init : Inits) BlockIdNameMap[Init.first] = Init.second; @@ -205,7 +207,13 @@ static const llvm::IndexedMap {TYPEDEF_USR, {"USR", &genSymbolIdAbbrev}}, {TYPEDEF_NAME, {"Name", &genStringAbbrev}}, {TYPEDEF_DEFLOCATION, {"DefLocation", &genLocationAbbrev}}, - {TYPEDEF_IS_USING, {"IsUsing", &genBoolAbbrev}}}; + {TYPEDEF_IS_USING, {"IsUsing", &genBoolAbbrev}}, + {CONCEPT_USR, {"USR", &genSymbolIdAbbrev}}, + {CONCEPT_NAME, {"Name", &genStringAbbrev}}, + {CONCEPT_IS_TYPE, {"IsType", &genBoolAbbrev}}, + {CONCEPT_CONSTRAINT_EXPRESSION, + {"ConstraintExpression", &genStringAbbrev}}, + {CONSTRAINT_EXPRESSION, {"Expression", &genStringAbbrev}}}; assert(Inits.size() == RecordIdCount); for (const auto &Init : Inits) { RecordIdNameMap[Init.first] = Init.second; @@ -263,7 +271,13 @@ static const std::vector>> // Template Blocks. {BI_TEMPLATE_BLOCK_ID, {}}, {BI_TEMPLATE_PARAM_BLOCK_ID, {TEMPLATE_PARAM_CONTENTS}}, - {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}}}; + {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}}, + // Concept Block + {BI_CONCEPT_BLOCK_ID, + {CONCEPT_USR, CONCEPT_NAME, CONCEPT_IS_TYPE, + CONCEPT_CONSTRAINT_EXPRESSION}}, + // Constraint Block + {BI_CONSTRAINT_BLOCK_ID, {CONSTRAINT_EXPRESSION}}}; // AbbreviationMap @@ -484,8 +498,9 @@ void ClangDocBitcodeWriter::emitBlock(const MemberTypeInfo &T) { void ClangDocBitcodeWriter::emitBlock(const CommentInfo &I) { StreamSubBlockGuard Block(Stream, BI_COMMENT_BLOCK_ID); + // Handle Kind (enum) separately, since it is not a string. + emitRecord(commentKindToString(I.Kind), COMMENT_KIND); for (const auto &L : std::vector>{ - {I.Kind, COMMENT_KIND}, {I.Text, COMMENT_TEXT}, {I.Name, COMMENT_NAME}, {I.Direction, COMMENT_DIRECTION}, @@ -523,6 +538,8 @@ void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) { emitBlock(C); for (const auto &C : I.Children.Typedefs) emitBlock(C); + for (const auto &C : I.Children.Concepts) + emitBlock(C); } void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) { @@ -626,12 +643,25 @@ void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) { emitBlock(*I.Template); } +void ClangDocBitcodeWriter::emitBlock(const ConceptInfo &I) { + StreamSubBlockGuard Block(Stream, BI_CONCEPT_BLOCK_ID); + emitRecord(I.USR, CONCEPT_USR); + emitRecord(I.Name, CONCEPT_NAME); + for (const auto &CI : I.Description) + emitBlock(CI); + emitRecord(I.IsType, CONCEPT_IS_TYPE); + emitRecord(I.ConstraintExpression, CONCEPT_CONSTRAINT_EXPRESSION); + emitBlock(I.Template); +} + void ClangDocBitcodeWriter::emitBlock(const TemplateInfo &T) { StreamSubBlockGuard Block(Stream, BI_TEMPLATE_BLOCK_ID); for (const auto &P : T.Params) emitBlock(P); if (T.Specialization) emitBlock(*T.Specialization); + for (const auto &C : T.Constraints) + emitBlock(C); } void ClangDocBitcodeWriter::emitBlock(const TemplateSpecializationInfo &T) { @@ -646,6 +676,12 @@ void ClangDocBitcodeWriter::emitBlock(const TemplateParamInfo &T) { emitRecord(T.Contents, TEMPLATE_PARAM_CONTENTS); } +void ClangDocBitcodeWriter::emitBlock(const ConstraintInfo &C) { + StreamSubBlockGuard Block(Stream, BI_CONSTRAINT_BLOCK_ID); + emitRecord(C.ConstraintExpr, CONSTRAINT_EXPRESSION); + emitBlock(C.ConceptRef, FieldId::F_concept); +} + bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) { switch (I->IT) { case InfoType::IT_namespace: @@ -663,7 +699,10 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) { case InfoType::IT_typedef: emitBlock(*static_cast(I)); break; - default: + case InfoType::IT_concept: + emitBlock(*static_cast(I)); + break; + case InfoType::IT_default: llvm::errs() << "Unexpected info, unable to write.\n"; return true; } diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h index e33a1aece883..4d0c0c07805e 100644 --- a/clang-tools-extra/clang-doc/BitcodeWriter.h +++ b/clang-tools-extra/clang-doc/BitcodeWriter.h @@ -66,7 +66,9 @@ enum BlockId { BI_TEMPLATE_BLOCK_ID, BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, BI_TEMPLATE_PARAM_BLOCK_ID, + BI_CONSTRAINT_BLOCK_ID, BI_TYPEDEF_BLOCK_ID, + BI_CONCEPT_BLOCK_ID, BI_LAST, BI_FIRST = BI_VERSION_BLOCK_ID }; @@ -135,6 +137,11 @@ enum RecordId { TYPEDEF_NAME, TYPEDEF_DEFLOCATION, TYPEDEF_IS_USING, + CONCEPT_USR, + CONCEPT_NAME, + CONCEPT_IS_TYPE, + CONCEPT_CONSTRAINT_EXPRESSION, + CONSTRAINT_EXPRESSION, RI_LAST, RI_FIRST = VERSION }; @@ -150,7 +157,8 @@ enum class FieldId { F_vparent, F_type, F_child_namespace, - F_child_record + F_child_record, + F_concept }; class ClangDocBitcodeWriter { @@ -179,6 +187,8 @@ class ClangDocBitcodeWriter { void emitBlock(const TemplateInfo &T); void emitBlock(const TemplateSpecializationInfo &T); void emitBlock(const TemplateParamInfo &T); + void emitBlock(const ConceptInfo &T); + void emitBlock(const ConstraintInfo &T); void emitBlock(const Reference &B, FieldId F); private: diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 79563c41435e..5989e5fe60cf 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -17,6 +17,7 @@ add_clang_library(clangDoc STATIC Serialize.cpp YAMLGenerator.cpp HTMLMustacheGenerator.cpp + JSONGenerator.cpp DEPENDS omp_gen diff --git a/clang-tools-extra/clang-doc/Generators.cpp b/clang-tools-extra/clang-doc/Generators.cpp index a3c2773412cf..3fb5b63c403a 100644 --- a/clang-tools-extra/clang-doc/Generators.cpp +++ b/clang-tools-extra/clang-doc/Generators.cpp @@ -105,5 +105,7 @@ static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest = HTMLGeneratorAnchorSource; static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest = MHTMLGeneratorAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED JSONGeneratorAnchorDest = + JSONGeneratorAnchorSource; } // namespace doc } // namespace clang diff --git a/clang-tools-extra/clang-doc/Generators.h b/clang-tools-extra/clang-doc/Generators.h index aee04b9d58d9..92d3006e6002 100644 --- a/clang-tools-extra/clang-doc/Generators.h +++ b/clang-tools-extra/clang-doc/Generators.h @@ -58,6 +58,7 @@ extern volatile int YAMLGeneratorAnchorSource; extern volatile int MDGeneratorAnchorSource; extern volatile int HTMLGeneratorAnchorSource; extern volatile int MHTMLGeneratorAnchorSource; +extern volatile int JSONGeneratorAnchorSource; } // namespace doc } // namespace clang diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 93b9279462a8..935bbfee7a9b 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -635,7 +635,8 @@ genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList) { } static std::unique_ptr genHTML(const CommentInfo &I) { - if (I.Kind == "FullComment") { + switch (I.Kind) { + case CommentKind::CK_FullComment: { auto FullComment = std::make_unique(HTMLTag::TAG_DIV); for (const auto &Child : I.Children) { std::unique_ptr Node = genHTML(*Child); @@ -645,7 +646,7 @@ static std::unique_ptr genHTML(const CommentInfo &I) { return std::move(FullComment); } - if (I.Kind == "ParagraphComment") { + case CommentKind::CK_ParagraphComment: { auto ParagraphComment = std::make_unique(HTMLTag::TAG_P); for (const auto &Child : I.Children) { std::unique_ptr Node = genHTML(*Child); @@ -657,7 +658,7 @@ static std::unique_ptr genHTML(const CommentInfo &I) { return std::move(ParagraphComment); } - if (I.Kind == "BlockCommandComment") { + case CommentKind::CK_BlockCommandComment: { auto BlockComment = std::make_unique(HTMLTag::TAG_DIV); BlockComment->Children.emplace_back( std::make_unique(HTMLTag::TAG_DIV, I.Name)); @@ -670,12 +671,26 @@ static std::unique_ptr genHTML(const CommentInfo &I) { return nullptr; return std::move(BlockComment); } - if (I.Kind == "TextComment") { - if (I.Text == "") + + case CommentKind::CK_TextComment: { + if (I.Text.empty()) return nullptr; return std::make_unique(I.Text); } - return nullptr; + + // For now, return nullptr for unsupported comment kinds + case CommentKind::CK_InlineCommandComment: + case CommentKind::CK_HTMLStartTagComment: + case CommentKind::CK_HTMLEndTagComment: + case CommentKind::CK_ParamCommandComment: + case CommentKind::CK_TParamCommandComment: + case CommentKind::CK_VerbatimBlockComment: + case CommentKind::CK_VerbatimBlockLineComment: + case CommentKind::CK_VerbatimLineComment: + case CommentKind::CK_Unknown: + return nullptr; + } + llvm_unreachable("Unhandled CommentKind"); } static std::unique_ptr genHTML(const std::vector &C) { @@ -970,6 +985,8 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, MainContentNodes = genHTML(*static_cast(I), CDCtx, InfoTitle); break; + case InfoType::IT_concept: + break; case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected info type"); @@ -996,6 +1013,8 @@ static std::string getRefType(InfoType IT) { return "enum"; case InfoType::IT_typedef: return "typedef"; + case InfoType::IT_concept: + return "concept"; } llvm_unreachable("Unknown InfoType"); } diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 8b7e4896c9fd..81ba99c21e37 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -208,37 +208,110 @@ static json::Value extractValue(const TypedefInfo &I) { } static json::Value extractValue(const CommentInfo &I) { - assert((I.Kind == "BlockCommandComment" || I.Kind == "FullComment" || - I.Kind == "ParagraphComment" || I.Kind == "TextComment") && - "Unknown Comment type in CommentInfo."); - Object Obj = Object(); - json::Value Child = Object(); - // TextComment has no children, so return it. - if (I.Kind == "TextComment") { - Obj.insert({"TextComment", I.Text}); - return Obj; - } + json::Value ChildVal = Object(); + Object &Child = *ChildVal.getAsObject(); - // BlockCommandComment needs to generate a Command key. - if (I.Kind == "BlockCommandComment") - Child.getAsObject()->insert({"Command", I.Name}); - - // Use the same handling for everything else. - // Only valid for: - // - BlockCommandComment - // - FullComment - // - ParagraphComment json::Value ChildArr = Array(); auto &CARef = *ChildArr.getAsArray(); CARef.reserve(I.Children.size()); for (const auto &C : I.Children) CARef.emplace_back(extractValue(*C)); - Child.getAsObject()->insert({"Children", ChildArr}); - Obj.insert({I.Kind, Child}); - return Obj; + switch (I.Kind) { + case CommentKind::CK_TextComment: { + Obj.insert({commentKindToString(I.Kind), I.Text}); + return Obj; + } + + case CommentKind::CK_BlockCommandComment: { + Child.insert({"Command", I.Name}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_InlineCommandComment: { + json::Value ArgsArr = Array(); + auto &ARef = *ArgsArr.getAsArray(); + ARef.reserve(I.Args.size()); + for (const auto &Arg : I.Args) + ARef.emplace_back(Arg); + Child.insert({"Command", I.Name}); + Child.insert({"Args", ArgsArr}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_ParamCommandComment: + case CommentKind::CK_TParamCommandComment: { + Child.insert({"ParamName", I.ParamName}); + Child.insert({"Direction", I.Direction}); + Child.insert({"Explicit", I.Explicit}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_VerbatimBlockComment: { + Child.insert({"Text", I.Text}); + if (!I.CloseName.empty()) + Child.insert({"CloseName", I.CloseName}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_VerbatimBlockLineComment: + case CommentKind::CK_VerbatimLineComment: { + Child.insert({"Text", I.Text}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_HTMLStartTagComment: { + json::Value AttrKeysArray = json::Array(); + json::Value AttrValuesArray = json::Array(); + auto &KeyArr = *AttrKeysArray.getAsArray(); + auto &ValArr = *AttrValuesArray.getAsArray(); + KeyArr.reserve(I.AttrKeys.size()); + ValArr.reserve(I.AttrValues.size()); + for (const auto &K : I.AttrKeys) + KeyArr.emplace_back(K); + for (const auto &V : I.AttrValues) + ValArr.emplace_back(V); + Child.insert({"Name", I.Name}); + Child.insert({"SelfClosing", I.SelfClosing}); + Child.insert({"AttrKeys", AttrKeysArray}); + Child.insert({"AttrValues", AttrValuesArray}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_HTMLEndTagComment: { + Child.insert({"Name", I.Name}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_FullComment: + case CommentKind::CK_ParagraphComment: { + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_Unknown: { + Obj.insert({commentKindToString(I.Kind), I.Text}); + return Obj; + } + } + llvm_unreachable("Unknown comment kind encountered."); } static void maybeInsertLocation(std::optional Loc, @@ -255,6 +328,7 @@ static void extractDescriptionFromInfo(ArrayRef Descriptions, return; json::Value DescArr = Array(); json::Array &DescARef = *DescArr.getAsArray(); + DescARef.reserve(Descriptions.size()); for (const CommentInfo &Child : Descriptions) DescARef.emplace_back(extractValue(Child)); EnumValObj.insert({"EnumValueComments", DescArr}); @@ -270,6 +344,7 @@ static json::Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, json::Value ParamArr = Array(); json::Array &ParamARef = *ParamArr.getAsArray(); + ParamARef.reserve(I.Params.size()); for (const auto Val : enumerate(I.Params)) { json::Value V = Object(); auto &VRef = *V.getAsObject(); @@ -289,14 +364,14 @@ static json::Value extractValue(const EnumInfo &I, Object Obj = Object(); std::string EnumType = I.Scoped ? "enum class " : "enum "; EnumType += I.Name; - bool HasComment = std::any_of( - I.Members.begin(), I.Members.end(), - [](const EnumValueInfo &M) { return !M.Description.empty(); }); + bool HasComment = llvm::any_of( + I.Members, [](const EnumValueInfo &M) { return !M.Description.empty(); }); Obj.insert({"EnumName", EnumType}); Obj.insert({"HasComment", HasComment}); Obj.insert({"ID", toHex(toStringRef(I.USR))}); json::Value EnumArr = Array(); json::Array &EnumARef = *EnumArr.getAsArray(); + EnumARef.reserve(I.Members.size()); for (const EnumValueInfo &M : I.Members) { json::Value EnumValue = Object(); auto &EnumValObj = *EnumValue.getAsObject(); @@ -322,6 +397,7 @@ static void extractScopeChildren(const ScopeChildren &S, Object &Obj, const ClangDocContext &CDCtx) { json::Value NamespaceArr = Array(); json::Array &NamespaceARef = *NamespaceArr.getAsArray(); + NamespaceARef.reserve(S.Namespaces.size()); for (const Reference &Child : S.Namespaces) NamespaceARef.emplace_back(extractValue(Child, ParentInfoDir)); @@ -330,6 +406,7 @@ static void extractScopeChildren(const ScopeChildren &S, Object &Obj, json::Value RecordArr = Array(); json::Array &RecordARef = *RecordArr.getAsArray(); + RecordARef.reserve(S.Records.size()); for (const Reference &Child : S.Records) RecordARef.emplace_back(extractValue(Child, ParentInfoDir)); @@ -338,12 +415,15 @@ static void extractScopeChildren(const ScopeChildren &S, Object &Obj, json::Value FunctionArr = Array(); json::Array &FunctionARef = *FunctionArr.getAsArray(); + FunctionARef.reserve(S.Functions.size()); json::Value PublicFunctionArr = Array(); json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray(); + PublicFunctionARef.reserve(S.Functions.size()); json::Value ProtectedFunctionArr = Array(); json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray(); + ProtectedFunctionARef.reserve(S.Functions.size()); for (const FunctionInfo &Child : S.Functions) { json::Value F = extractValue(Child, ParentInfoDir, CDCtx); @@ -367,6 +447,7 @@ static void extractScopeChildren(const ScopeChildren &S, Object &Obj, json::Value EnumArr = Array(); auto &EnumARef = *EnumArr.getAsArray(); + EnumARef.reserve(S.Enums.size()); for (const EnumInfo &Child : S.Enums) EnumARef.emplace_back(extractValue(Child, CDCtx)); @@ -375,6 +456,7 @@ static void extractScopeChildren(const ScopeChildren &S, Object &Obj, json::Value TypedefArr = Array(); auto &TypedefARef = *TypedefArr.getAsArray(); + TypedefARef.reserve(S.Typedefs.size()); for (const TypedefInfo &Child : S.Typedefs) TypedefARef.emplace_back(extractValue(Child)); @@ -411,10 +493,13 @@ static json::Value extractValue(const RecordInfo &I, extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx); json::Value PublicMembers = Array(); json::Array &PubMemberRef = *PublicMembers.getAsArray(); + PubMemberRef.reserve(I.Members.size()); json::Value ProtectedMembers = Array(); json::Array &ProtMemberRef = *ProtectedMembers.getAsArray(); + ProtMemberRef.reserve(I.Members.size()); json::Value PrivateMembers = Array(); json::Array &PrivMemberRef = *PrivateMembers.getAsArray(); + PrivMemberRef.reserve(I.Members.size()); for (const MemberTypeInfo &Member : I.Members) { json::Value MemberValue = Object(); auto &MVRef = *MemberValue.getAsObject(); @@ -446,20 +531,25 @@ static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V, auto InfoPath = I->getRelativeFilePath(""); SmallString<128> RelativePath = computeRelativePath("", InfoPath); sys::path::native(RelativePath, sys::path::Style::posix); + + auto *SSA = StylesheetArr.getAsArray(); + SSA->reserve(CDCtx.UserStylesheets.size()); for (const auto &FilePath : CDCtx.UserStylesheets) { SmallString<128> StylesheetPath = RelativePath; sys::path::append(StylesheetPath, sys::path::Style::posix, sys::path::filename(FilePath)); - StylesheetArr.getAsArray()->emplace_back(StylesheetPath); + SSA->emplace_back(StylesheetPath); } V.getAsObject()->insert({"Stylesheets", StylesheetArr}); json::Value ScriptArr = Array(); + auto *SCA = ScriptArr.getAsArray(); + SCA->reserve(CDCtx.JsScripts.size()); for (auto Script : CDCtx.JsScripts) { SmallString<128> JsPath = RelativePath; sys::path::append(JsPath, sys::path::Style::posix, sys::path::filename(Script)); - ScriptArr.getAsArray()->emplace_back(JsPath); + SCA->emplace_back(JsPath); } V.getAsObject()->insert({"Scripts", ScriptArr}); return Error::success(); @@ -495,6 +585,8 @@ Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS, case InfoType::IT_typedef: OS << "IT_typedef\n"; break; + case InfoType::IT_concept: + break; case InfoType::IT_default: return createStringError(inconvertibleErrorCode(), "unexpected InfoType"); } diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp new file mode 100644 index 000000000000..8a37621597c6 --- /dev/null +++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp @@ -0,0 +1,592 @@ +#include "Generators.h" +#include "clang/Basic/Specifiers.h" +#include "llvm/Support/JSON.h" + +using namespace llvm; +using namespace llvm::json; + +namespace clang { +namespace doc { + +class JSONGenerator : public Generator { +public: + static const char *Format; + + Error generateDocs(StringRef RootDir, + llvm::StringMap> Infos, + const ClangDocContext &CDCtx) override; + Error createResources(ClangDocContext &CDCtx) override; + Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, + const ClangDocContext &CDCtx) override; +}; + +const char *JSONGenerator::Format = "json"; + +static void serializeInfo(const TypedefInfo &I, json::Object &Obj, + std::optional RepositoryUrl); +static void serializeInfo(const EnumInfo &I, json::Object &Obj, + std::optional RepositoryUrl); +static void serializeInfo(const ConstraintInfo &I, Object &Obj); + +// Convenience lambda to pass to serializeArray. +// If a serializeInfo needs a RepositoryUrl, create a local lambda that captures +// the optional. +static auto SerializeInfoLambda = [](const ConstraintInfo &Info, + Object &Object) { + serializeInfo(Info, Object); +}; + +static json::Object serializeLocation(const Location &Loc, + std::optional RepositoryUrl) { + Object LocationObj = Object(); + LocationObj["LineNumber"] = Loc.StartLineNumber; + LocationObj["Filename"] = Loc.Filename; + + if (!Loc.IsFileInRootDir || !RepositoryUrl) + return LocationObj; + SmallString<128> FileURL(*RepositoryUrl); + sys::path::append(FileURL, sys::path::Style::posix, Loc.Filename); + FileURL += "#" + std::to_string(Loc.StartLineNumber); + LocationObj["FileURL"] = FileURL; + return LocationObj; +} + +static json::Value serializeComment(const CommentInfo &I) { + // taken from PR #142273 + Object Obj = Object(); + + json::Value ChildVal = Object(); + Object &Child = *ChildVal.getAsObject(); + + json::Value ChildArr = Array(); + auto &CARef = *ChildArr.getAsArray(); + CARef.reserve(I.Children.size()); + for (const auto &C : I.Children) + CARef.emplace_back(serializeComment(*C)); + + switch (I.Kind) { + case CommentKind::CK_TextComment: { + Obj.insert({commentKindToString(I.Kind), I.Text}); + return Obj; + } + + case CommentKind::CK_BlockCommandComment: { + Child.insert({"Command", I.Name}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_InlineCommandComment: { + json::Value ArgsArr = Array(); + auto &ARef = *ArgsArr.getAsArray(); + ARef.reserve(I.Args.size()); + for (const auto &Arg : I.Args) + ARef.emplace_back(Arg); + Child.insert({"Command", I.Name}); + Child.insert({"Args", ArgsArr}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_ParamCommandComment: + case CommentKind::CK_TParamCommandComment: { + Child.insert({"ParamName", I.ParamName}); + Child.insert({"Direction", I.Direction}); + Child.insert({"Explicit", I.Explicit}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_VerbatimBlockComment: { + Child.insert({"Text", I.Text}); + if (!I.CloseName.empty()) + Child.insert({"CloseName", I.CloseName}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_VerbatimBlockLineComment: + case CommentKind::CK_VerbatimLineComment: { + Child.insert({"Text", I.Text}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_HTMLStartTagComment: { + json::Value AttrKeysArray = json::Array(); + json::Value AttrValuesArray = json::Array(); + auto &KeyArr = *AttrKeysArray.getAsArray(); + auto &ValArr = *AttrValuesArray.getAsArray(); + KeyArr.reserve(I.AttrKeys.size()); + ValArr.reserve(I.AttrValues.size()); + for (const auto &K : I.AttrKeys) + KeyArr.emplace_back(K); + for (const auto &V : I.AttrValues) + ValArr.emplace_back(V); + Child.insert({"Name", I.Name}); + Child.insert({"SelfClosing", I.SelfClosing}); + Child.insert({"AttrKeys", AttrKeysArray}); + Child.insert({"AttrValues", AttrValuesArray}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_HTMLEndTagComment: { + Child.insert({"Name", I.Name}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_FullComment: + case CommentKind::CK_ParagraphComment: { + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_Unknown: { + Obj.insert({commentKindToString(I.Kind), I.Text}); + return Obj; + } + } + llvm_unreachable("Unknown comment kind encountered."); +} + +static void serializeCommonAttributes(const Info &I, json::Object &Obj, + std::optional RepositoryUrl) { + Obj["Name"] = I.Name; + Obj["USR"] = toHex(toStringRef(I.USR)); + + if (!I.Path.empty()) + Obj["Path"] = I.Path; + + if (!I.Namespace.empty()) { + Obj["Namespace"] = json::Array(); + for (const auto &NS : I.Namespace) + Obj["Namespace"].getAsArray()->push_back(NS.Name); + } + + if (!I.Description.empty()) { + json::Value DescArray = json::Array(); + auto &DescArrayRef = *DescArray.getAsArray(); + DescArrayRef.reserve(I.Description.size()); + for (const auto &Comment : I.Description) + DescArrayRef.push_back(serializeComment(Comment)); + Obj["Description"] = DescArray; + } + + // Namespaces aren't SymbolInfos, so they dont have a DefLoc + if (I.IT != InfoType::IT_namespace) { + const auto *Symbol = static_cast(&I); + if (Symbol->DefLoc) + Obj["Location"] = + serializeLocation(Symbol->DefLoc.value(), RepositoryUrl); + } +} + +static void serializeReference(const Reference &Ref, Object &ReferenceObj) { + ReferenceObj["Path"] = Ref.Path; + ReferenceObj["Name"] = Ref.Name; + ReferenceObj["QualName"] = Ref.QualName; + ReferenceObj["USR"] = toHex(toStringRef(Ref.USR)); +} + +static void serializeReference(const SmallVector &References, + Object &Obj, std::string Key) { + json::Value ReferencesArray = Array(); + json::Array &ReferencesArrayRef = *ReferencesArray.getAsArray(); + ReferencesArrayRef.reserve(References.size()); + for (const auto &Reference : References) { + json::Value ReferenceVal = Object(); + auto &ReferenceObj = *ReferenceVal.getAsObject(); + serializeReference(Reference, ReferenceObj); + ReferencesArrayRef.push_back(ReferenceVal); + } + Obj[Key] = ReferencesArray; +} + +// Although namespaces and records both have ScopeChildren, they serialize them +// differently. Only enums, records, and typedefs are handled here. +static void serializeCommonChildren(const ScopeChildren &Children, + json::Object &Obj, + std::optional RepositoryUrl) { + if (!Children.Enums.empty()) { + json::Value EnumsArray = Array(); + auto &EnumsArrayRef = *EnumsArray.getAsArray(); + EnumsArrayRef.reserve(Children.Enums.size()); + for (const auto &Enum : Children.Enums) { + json::Value EnumVal = Object(); + auto &EnumObj = *EnumVal.getAsObject(); + serializeInfo(Enum, EnumObj, RepositoryUrl); + EnumsArrayRef.push_back(EnumVal); + } + Obj["Enums"] = EnumsArray; + } + + if (!Children.Typedefs.empty()) { + json::Value TypedefsArray = Array(); + auto &TypedefsArrayRef = *TypedefsArray.getAsArray(); + TypedefsArrayRef.reserve(Children.Typedefs.size()); + for (const auto &Typedef : Children.Typedefs) { + json::Value TypedefVal = Object(); + auto &TypedefObj = *TypedefVal.getAsObject(); + serializeInfo(Typedef, TypedefObj, RepositoryUrl); + TypedefsArrayRef.push_back(TypedefVal); + } + Obj["Typedefs"] = TypedefsArray; + } + + if (!Children.Records.empty()) { + json::Value RecordsArray = Array(); + auto &RecordsArrayRef = *RecordsArray.getAsArray(); + RecordsArrayRef.reserve(Children.Records.size()); + for (const auto &Record : Children.Records) { + json::Value RecordVal = Object(); + auto &RecordObj = *RecordVal.getAsObject(); + serializeReference(Record, RecordObj); + RecordsArrayRef.push_back(RecordVal); + } + Obj["Records"] = RecordsArray; + } +} + +template +static void serializeArray(const std::vector &Records, Object &Obj, + const std::string &Key, + SerializationFunc SerializeInfo) { + json::Value RecordsArray = Array(); + auto &RecordsArrayRef = *RecordsArray.getAsArray(); + RecordsArrayRef.reserve(Records.size()); + for (const auto &Item : Records) { + json::Value ItemVal = Object(); + auto &ItemObj = *ItemVal.getAsObject(); + SerializeInfo(Item, ItemObj); + RecordsArrayRef.push_back(ItemVal); + } + Obj[Key] = RecordsArray; +} + +static void serializeInfo(const ConstraintInfo &I, Object &Obj) { + serializeReference(I.ConceptRef, Obj); + Obj["Expression"] = I.ConstraintExpr; +} + +static void serializeInfo(const TemplateInfo &Template, Object &Obj) { + json::Value TemplateVal = Object(); + auto &TemplateObj = *TemplateVal.getAsObject(); + + if (Template.Specialization) { + json::Value TemplateSpecializationVal = Object(); + auto &TemplateSpecializationObj = *TemplateSpecializationVal.getAsObject(); + TemplateSpecializationObj["SpecializationOf"] = + toHex(toStringRef(Template.Specialization->SpecializationOf)); + if (!Template.Specialization->Params.empty()) { + json::Value ParamsArray = Array(); + auto &ParamsArrayRef = *ParamsArray.getAsArray(); + ParamsArrayRef.reserve(Template.Specialization->Params.size()); + for (const auto &Param : Template.Specialization->Params) + ParamsArrayRef.push_back(Param.Contents); + TemplateSpecializationObj["Parameters"] = ParamsArray; + } + TemplateObj["Specialization"] = TemplateSpecializationVal; + } + + if (!Template.Params.empty()) { + json::Value ParamsArray = Array(); + auto &ParamsArrayRef = *ParamsArray.getAsArray(); + ParamsArrayRef.reserve(Template.Params.size()); + for (const auto &Param : Template.Params) + ParamsArrayRef.push_back(Param.Contents); + TemplateObj["Parameters"] = ParamsArray; + } + + if (!Template.Constraints.empty()) + serializeArray(Template.Constraints, TemplateObj, "Constraints", + SerializeInfoLambda); + + Obj["Template"] = TemplateVal; +} + +static void serializeInfo(const ConceptInfo &I, Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["IsType"] = I.IsType; + Obj["ConstraintExpression"] = I.ConstraintExpression; + serializeInfo(I.Template, Obj); +} + +static void serializeInfo(const TypeInfo &I, Object &Obj) { + Obj["Name"] = I.Type.Name; + Obj["QualName"] = I.Type.QualName; + Obj["USR"] = toHex(toStringRef(I.Type.USR)); + Obj["IsTemplate"] = I.IsTemplate; + Obj["IsBuiltIn"] = I.IsBuiltIn; +} + +static void serializeInfo(const FunctionInfo &F, json::Object &Obj, + std::optional RepositoryURL) { + serializeCommonAttributes(F, Obj, RepositoryURL); + Obj["IsStatic"] = F.IsStatic; + + auto ReturnTypeObj = Object(); + serializeInfo(F.ReturnType, ReturnTypeObj); + Obj["ReturnType"] = std::move(ReturnTypeObj); + + if (!F.Params.empty()) { + json::Value ParamsArray = json::Array(); + auto &ParamsArrayRef = *ParamsArray.getAsArray(); + ParamsArrayRef.reserve(F.Params.size()); + for (const auto &Param : F.Params) { + json::Value ParamVal = Object(); + auto &ParamObj = *ParamVal.getAsObject(); + ParamObj["Name"] = Param.Name; + ParamObj["Type"] = Param.Type.Name; + ParamsArrayRef.push_back(ParamVal); + } + Obj["Params"] = ParamsArray; + } + + if (F.Template) + serializeInfo(F.Template.value(), Obj); +} + +static void serializeInfo(const EnumInfo &I, json::Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["Scoped"] = I.Scoped; + + if (I.BaseType) { + json::Value BaseTypeVal = Object(); + auto &BaseTypeObj = *BaseTypeVal.getAsObject(); + BaseTypeObj["Name"] = I.BaseType->Type.Name; + BaseTypeObj["QualName"] = I.BaseType->Type.QualName; + BaseTypeObj["USR"] = toHex(toStringRef(I.BaseType->Type.USR)); + Obj["BaseType"] = BaseTypeVal; + } + + if (!I.Members.empty()) { + json::Value MembersArray = Array(); + auto &MembersArrayRef = *MembersArray.getAsArray(); + MembersArrayRef.reserve(I.Members.size()); + for (const auto &Member : I.Members) { + json::Value MemberVal = Object(); + auto &MemberObj = *MemberVal.getAsObject(); + MemberObj["Name"] = Member.Name; + if (!Member.ValueExpr.empty()) + MemberObj["ValueExpr"] = Member.ValueExpr; + else + MemberObj["Value"] = Member.Value; + MembersArrayRef.push_back(MemberVal); + } + Obj["Members"] = MembersArray; + } +} + +static void serializeInfo(const TypedefInfo &I, json::Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["TypeDeclaration"] = I.TypeDeclaration; + Obj["IsUsing"] = I.IsUsing; + json::Value TypeVal = Object(); + auto &TypeObj = *TypeVal.getAsObject(); + serializeInfo(I.Underlying, TypeObj); + Obj["Underlying"] = TypeVal; +} + +static void serializeInfo(const RecordInfo &I, json::Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["FullName"] = I.FullName; + Obj["TagType"] = getTagType(I.TagType); + Obj["IsTypedef"] = I.IsTypeDef; + + if (!I.Children.Functions.empty()) { + json::Value PubFunctionsArray = Array(); + json::Array &PubFunctionsArrayRef = *PubFunctionsArray.getAsArray(); + json::Value ProtFunctionsArray = Array(); + json::Array &ProtFunctionsArrayRef = *ProtFunctionsArray.getAsArray(); + + for (const auto &Function : I.Children.Functions) { + json::Value FunctionVal = Object(); + auto &FunctionObj = *FunctionVal.getAsObject(); + serializeInfo(Function, FunctionObj, RepositoryUrl); + AccessSpecifier Access = Function.Access; + if (Access == AccessSpecifier::AS_public) + PubFunctionsArrayRef.push_back(FunctionVal); + else if (Access == AccessSpecifier::AS_protected) + ProtFunctionsArrayRef.push_back(FunctionVal); + } + + if (!PubFunctionsArrayRef.empty()) + Obj["PublicFunctions"] = PubFunctionsArray; + if (!ProtFunctionsArrayRef.empty()) + Obj["ProtectedFunctions"] = ProtFunctionsArray; + } + + if (!I.Members.empty()) { + json::Value PublicMembersArray = Array(); + json::Array &PubMembersArrayRef = *PublicMembersArray.getAsArray(); + json::Value ProtectedMembersArray = Array(); + json::Array &ProtMembersArrayRef = *ProtectedMembersArray.getAsArray(); + + for (const MemberTypeInfo &Member : I.Members) { + json::Value MemberVal = Object(); + auto &MemberObj = *MemberVal.getAsObject(); + MemberObj["Name"] = Member.Name; + MemberObj["Type"] = Member.Type.Name; + + if (Member.Access == AccessSpecifier::AS_public) + PubMembersArrayRef.push_back(MemberVal); + else if (Member.Access == AccessSpecifier::AS_protected) + ProtMembersArrayRef.push_back(MemberVal); + } + + if (!PubMembersArrayRef.empty()) + Obj["PublicMembers"] = PublicMembersArray; + if (!ProtMembersArrayRef.empty()) + Obj["ProtectedMembers"] = ProtectedMembersArray; + } + + if (!I.Bases.empty()) { + json::Value BasesArray = Array(); + json::Array &BasesArrayRef = *BasesArray.getAsArray(); + BasesArrayRef.reserve(I.Bases.size()); + for (const auto &BaseInfo : I.Bases) { + json::Value BaseInfoVal = Object(); + auto &BaseInfoObj = *BaseInfoVal.getAsObject(); + serializeInfo(BaseInfo, BaseInfoObj, RepositoryUrl); + BaseInfoObj["IsVirtual"] = BaseInfo.IsVirtual; + BaseInfoObj["Access"] = getAccessSpelling(BaseInfo.Access); + BaseInfoObj["IsParent"] = BaseInfo.IsParent; + BasesArrayRef.push_back(BaseInfoVal); + } + Obj["Bases"] = BasesArray; + } + + if (!I.Parents.empty()) + serializeReference(I.Parents, Obj, "Parents"); + + if (!I.VirtualParents.empty()) + serializeReference(I.VirtualParents, Obj, "VirtualParents"); + + if (I.Template) + serializeInfo(I.Template.value(), Obj); + + serializeCommonChildren(I.Children, Obj, RepositoryUrl); +} + +static void serializeInfo(const NamespaceInfo &I, json::Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + + if (!I.Children.Namespaces.empty()) { + json::Value NamespacesArray = Array(); + auto &NamespacesArrayRef = *NamespacesArray.getAsArray(); + NamespacesArrayRef.reserve(I.Children.Namespaces.size()); + for (auto &Namespace : I.Children.Namespaces) { + json::Value NamespaceVal = Object(); + auto &NamespaceObj = *NamespaceVal.getAsObject(); + serializeReference(Namespace, NamespaceObj); + NamespacesArrayRef.push_back(NamespaceVal); + } + Obj["Namespaces"] = NamespacesArray; + } + + auto SerializeInfo = [RepositoryUrl](const auto &Info, Object &Object) { + serializeInfo(Info, Object, RepositoryUrl); + }; + + if (!I.Children.Functions.empty()) { + json::Value FunctionsArray = Array(); + auto &FunctionsArrayRef = *FunctionsArray.getAsArray(); + FunctionsArrayRef.reserve(I.Children.Functions.size()); + for (const auto &Function : I.Children.Functions) { + json::Value FunctionVal = Object(); + auto &FunctionObj = *FunctionVal.getAsObject(); + serializeInfo(Function, FunctionObj, RepositoryUrl); + FunctionsArrayRef.push_back(FunctionVal); + } + Obj["Functions"] = FunctionsArray; + } + + if (!I.Children.Concepts.empty()) + serializeArray(I.Children.Concepts, Obj, "Concepts", SerializeInfo); + + serializeCommonChildren(I.Children, Obj, RepositoryUrl); +} + +Error JSONGenerator::generateDocs( + StringRef RootDir, llvm::StringMap> Infos, + const ClangDocContext &CDCtx) { + StringSet<> CreatedDirs; + StringMap> FileToInfos; + for (const auto &Group : Infos) { + Info *Info = Group.getValue().get(); + + SmallString<128> Path; + sys::path::native(RootDir, Path); + sys::path::append(Path, Info->getRelativeFilePath("")); + if (!CreatedDirs.contains(Path)) { + if (std::error_code Err = sys::fs::create_directories(Path); + Err != std::error_code()) + return createFileError(Twine(Path), Err); + CreatedDirs.insert(Path); + } + + sys::path::append(Path, Info->getFileBaseName() + ".json"); + FileToInfos[Path].push_back(Info); + } + + for (const auto &Group : FileToInfos) { + std::error_code FileErr; + raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_Text); + if (FileErr) + return createFileError("cannot open file " + Group.getKey(), FileErr); + + for (const auto &Info : Group.getValue()) + if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) + return Err; + } + + return Error::success(); +} + +Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS, + const ClangDocContext &CDCtx) { + json::Object Obj = Object(); + + switch (I->IT) { + case InfoType::IT_namespace: + serializeInfo(*static_cast(I), Obj, CDCtx.RepositoryUrl); + break; + case InfoType::IT_record: + serializeInfo(*static_cast(I), Obj, CDCtx.RepositoryUrl); + break; + case InfoType::IT_concept: + case InfoType::IT_enum: + case InfoType::IT_function: + case InfoType::IT_typedef: + break; + case InfoType::IT_default: + return createStringError(inconvertibleErrorCode(), "unexpected info type"); + } + OS << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj))); + return Error::success(); +} + +Error JSONGenerator::createResources(ClangDocContext &CDCtx) { + return Error::success(); +} + +static GeneratorRegistry::Add JSON(JSONGenerator::Format, + "Generator for JSON output."); +volatile int JSONGeneratorAnchorSource = 0; +} // namespace doc +} // namespace clang diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp index ccd6175c96cb..6e68e09cfa2a 100644 --- a/clang-tools-extra/clang-doc/MDGenerator.cpp +++ b/clang-tools-extra/clang-doc/MDGenerator.cpp @@ -75,39 +75,49 @@ static void maybeWriteSourceFileRef(llvm::raw_ostream &OS, } static void writeDescription(const CommentInfo &I, raw_ostream &OS) { - if (I.Kind == "FullComment") { + switch (I.Kind) { + case CommentKind::CK_FullComment: for (const auto &Child : I.Children) writeDescription(*Child, OS); - } else if (I.Kind == "ParagraphComment") { + break; + + case CommentKind::CK_ParagraphComment: for (const auto &Child : I.Children) writeDescription(*Child, OS); writeNewLine(OS); - } else if (I.Kind == "BlockCommandComment") { + break; + + case CommentKind::CK_BlockCommandComment: OS << genEmphasis(I.Name); for (const auto &Child : I.Children) writeDescription(*Child, OS); - } else if (I.Kind == "InlineCommandComment") { + break; + + case CommentKind::CK_InlineCommandComment: OS << genEmphasis(I.Name) << " " << I.Text; - } else if (I.Kind == "ParamCommandComment") { - std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; - OS << genEmphasis(I.ParamName) << I.Text << Direction; - for (const auto &Child : I.Children) - writeDescription(*Child, OS); - } else if (I.Kind == "TParamCommandComment") { + break; + + case CommentKind::CK_ParamCommandComment: + case CommentKind::CK_TParamCommandComment: { std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; OS << genEmphasis(I.ParamName) << I.Text << Direction; for (const auto &Child : I.Children) writeDescription(*Child, OS); - } else if (I.Kind == "VerbatimBlockComment") { + break; + } + + case CommentKind::CK_VerbatimBlockComment: for (const auto &Child : I.Children) writeDescription(*Child, OS); - } else if (I.Kind == "VerbatimBlockLineComment") { - OS << I.Text; - writeNewLine(OS); - } else if (I.Kind == "VerbatimLineComment") { + break; + + case CommentKind::CK_VerbatimBlockLineComment: + case CommentKind::CK_VerbatimLineComment: OS << I.Text; writeNewLine(OS); - } else if (I.Kind == "HTMLStartTagComment") { + break; + + case CommentKind::CK_HTMLStartTagComment: { if (I.AttrKeys.size() != I.AttrValues.size()) return; std::string Buffer; @@ -117,12 +127,20 @@ static void writeDescription(const CommentInfo &I, raw_ostream &OS) { std::string CloseTag = I.SelfClosing ? "/>" : ">"; writeLine("<" + I.Name + Attrs.str() + CloseTag, OS); - } else if (I.Kind == "HTMLEndTagComment") { + break; + } + + case CommentKind::CK_HTMLEndTagComment: writeLine("", OS); - } else if (I.Kind == "TextComment") { + break; + + case CommentKind::CK_TextComment: OS << I.Text; - } else { - OS << "Unknown comment kind: " << I.Kind << ".\n\n"; + break; + + case CommentKind::CK_Unknown: + OS << "Unknown comment kind: " << static_cast(I.Kind) << ".\n\n"; + break; } } @@ -354,6 +372,9 @@ static llvm::Error genIndex(ClangDocContext &CDCtx) { case InfoType::IT_typedef: Type = "Typedef"; break; + case InfoType::IT_concept: + Type = "Concept"; + break; case InfoType::IT_default: Type = "Other"; } @@ -446,6 +467,8 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, case InfoType::IT_typedef: genMarkdown(CDCtx, *static_cast(I), OS); break; + case InfoType::IT_concept: + break; case InfoType::IT_default: return createStringError(llvm::inconvertibleErrorCode(), "unexpected InfoType"); diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp index 9f640b5325da..6021e17b4696 100644 --- a/clang-tools-extra/clang-doc/Mapper.cpp +++ b/clang-tools-extra/clang-doc/Mapper.cpp @@ -134,6 +134,10 @@ bool MapASTVisitor::VisitTypeAliasDecl(const TypeAliasDecl *D) { return mapDecl(D, /*isDefinition=*/true); } +bool MapASTVisitor::VisitConceptDecl(const ConceptDecl *D) { + return mapDecl(D, true); +} + comments::FullComment * MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const { RawComment *Comment = Context.getRawCommentForDeclNoCache(D); diff --git a/clang-tools-extra/clang-doc/Mapper.h b/clang-tools-extra/clang-doc/Mapper.h index 36322ea2bfb7..04dc5450c8ba 100644 --- a/clang-tools-extra/clang-doc/Mapper.h +++ b/clang-tools-extra/clang-doc/Mapper.h @@ -41,6 +41,7 @@ class MapASTVisitor : public clang::RecursiveASTVisitor, bool VisitFunctionDecl(const FunctionDecl *D); bool VisitTypedefDecl(const TypedefDecl *D); bool VisitTypeAliasDecl(const TypeAliasDecl *D); + bool VisitConceptDecl(const ConceptDecl *D); private: template bool mapDecl(const T *D, bool IsDefinition); diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp index ad93ed66b86a..286aeeea1001 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -20,12 +20,68 @@ // //===----------------------------------------------------------------------===// #include "Representation.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" namespace clang { namespace doc { +CommentKind stringToCommentKind(llvm::StringRef KindStr) { + static const llvm::StringMap KindMap = { + {"FullComment", CommentKind::CK_FullComment}, + {"ParagraphComment", CommentKind::CK_ParagraphComment}, + {"TextComment", CommentKind::CK_TextComment}, + {"InlineCommandComment", CommentKind::CK_InlineCommandComment}, + {"HTMLStartTagComment", CommentKind::CK_HTMLStartTagComment}, + {"HTMLEndTagComment", CommentKind::CK_HTMLEndTagComment}, + {"BlockCommandComment", CommentKind::CK_BlockCommandComment}, + {"ParamCommandComment", CommentKind::CK_ParamCommandComment}, + {"TParamCommandComment", CommentKind::CK_TParamCommandComment}, + {"VerbatimBlockComment", CommentKind::CK_VerbatimBlockComment}, + {"VerbatimBlockLineComment", CommentKind::CK_VerbatimBlockLineComment}, + {"VerbatimLineComment", CommentKind::CK_VerbatimLineComment}, + }; + + auto It = KindMap.find(KindStr); + if (It != KindMap.end()) { + return It->second; + } + return CommentKind::CK_Unknown; +} + +llvm::StringRef commentKindToString(CommentKind Kind) { + switch (Kind) { + case CommentKind::CK_FullComment: + return "FullComment"; + case CommentKind::CK_ParagraphComment: + return "ParagraphComment"; + case CommentKind::CK_TextComment: + return "TextComment"; + case CommentKind::CK_InlineCommandComment: + return "InlineCommandComment"; + case CommentKind::CK_HTMLStartTagComment: + return "HTMLStartTagComment"; + case CommentKind::CK_HTMLEndTagComment: + return "HTMLEndTagComment"; + case CommentKind::CK_BlockCommandComment: + return "BlockCommandComment"; + case CommentKind::CK_ParamCommandComment: + return "ParamCommandComment"; + case CommentKind::CK_TParamCommandComment: + return "TParamCommandComment"; + case CommentKind::CK_VerbatimBlockComment: + return "VerbatimBlockComment"; + case CommentKind::CK_VerbatimBlockLineComment: + return "VerbatimBlockLineComment"; + case CommentKind::CK_VerbatimLineComment: + return "VerbatimLineComment"; + case CommentKind::CK_Unknown: + return "Unknown"; + } + llvm_unreachable("Unhandled CommentKind"); +} + namespace { const SymbolID EmptySID = SymbolID(); @@ -87,10 +143,13 @@ mergeInfos(std::vector> &Values) { return reduce(Values); case InfoType::IT_typedef: return reduce(Values); - default: + case InfoType::IT_concept: + return reduce(Values); + case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected info type"); } + llvm_unreachable("unhandled enumerator"); } bool CommentInfo::operator==(const CommentInfo &Other) const { @@ -231,6 +290,7 @@ void NamespaceInfo::merge(NamespaceInfo &&Other) { reduceChildren(Children.Functions, std::move(Other.Children.Functions)); reduceChildren(Children.Enums, std::move(Other.Children.Enums)); reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs)); + reduceChildren(Children.Concepts, std::move(Other.Children.Concepts)); mergeBase(std::move(Other)); } @@ -295,6 +355,19 @@ void TypedefInfo::merge(TypedefInfo &&Other) { SymbolInfo::merge(std::move(Other)); } +void ConceptInfo::merge(ConceptInfo &&Other) { + assert(mergeable(Other)); + if (!IsType) + IsType = Other.IsType; + if (ConstraintExpression.empty()) + ConstraintExpression = std::move(Other.ConstraintExpression); + if (Template.Constraints.empty()) + Template.Constraints = std::move(Other.Template.Constraints); + if (Template.Params.empty()) + Template.Params = std::move(Other.Template.Params); + SymbolInfo::merge(std::move(Other)); +} + BaseRecordInfo::BaseRecordInfo() : RecordInfo() {} BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, @@ -331,6 +404,9 @@ llvm::SmallString<16> Info::extractName() const { case InfoType::IT_function: return llvm::SmallString<16>("@nonymous_function_" + toHex(llvm::toStringRef(USR))); + case InfoType::IT_concept: + return llvm::SmallString<16>("@nonymous_concept_" + + toHex(llvm::toStringRef(USR))); case InfoType::IT_default: return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR))); } @@ -396,6 +472,7 @@ void ScopeChildren::sort() { llvm::sort(Functions.begin(), Functions.end()); llvm::sort(Enums.begin(), Enums.end()); llvm::sort(Typedefs.begin(), Typedefs.end()); + llvm::sort(Concepts.begin(), Concepts.end()); } } // namespace doc } // namespace clang diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h index 0dbf73452fed..b23069f2bd32 100644 --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -35,6 +35,7 @@ struct EnumInfo; struct FunctionInfo; struct Info; struct TypedefInfo; +struct ConceptInfo; enum class InfoType { IT_default, @@ -42,9 +43,29 @@ enum class InfoType { IT_record, IT_function, IT_enum, - IT_typedef + IT_typedef, + IT_concept }; +enum class CommentKind { + CK_FullComment, + CK_ParagraphComment, + CK_TextComment, + CK_InlineCommandComment, + CK_HTMLStartTagComment, + CK_HTMLEndTagComment, + CK_BlockCommandComment, + CK_ParamCommandComment, + CK_TParamCommandComment, + CK_VerbatimBlockComment, + CK_VerbatimBlockLineComment, + CK_VerbatimLineComment, + CK_Unknown +}; + +CommentKind stringToCommentKind(llvm::StringRef KindStr); +llvm::StringRef commentKindToString(CommentKind Kind); + // A representation of a parsed comment. struct CommentInfo { CommentInfo() = default; @@ -60,13 +81,13 @@ struct CommentInfo { // the vector. bool operator<(const CommentInfo &Other) const; - // TODO: The Kind field should be an enum, so we can switch on it easily. - SmallString<16> - Kind; // Kind of comment (FullComment, ParagraphComment, TextComment, - // InlineCommandComment, HTMLStartTagComment, HTMLEndTagComment, - // BlockCommandComment, ParamCommandComment, - // TParamCommandComment, VerbatimBlockComment, - // VerbatimBlockLineComment, VerbatimLineComment). + CommentKind Kind = CommentKind:: + CK_Unknown; // Kind of comment (FullComment, ParagraphComment, + // TextComment, InlineCommandComment, HTMLStartTagComment, + // HTMLEndTagComment, BlockCommandComment, + // ParamCommandComment, TParamCommandComment, + // VerbatimBlockComment, VerbatimBlockLineComment, + // VerbatimLineComment). SmallString<64> Text; // Text of the comment. SmallString<16> Name; // Name of the comment (for Verbatim and HTML). SmallString<8> Direction; // Parameter direction (for (T)ParamCommand). @@ -147,6 +168,7 @@ struct ScopeChildren { std::vector Functions; std::vector Enums; std::vector Typedefs; + std::vector Concepts; void sort(); }; @@ -192,6 +214,15 @@ struct TemplateSpecializationInfo { std::vector Params; }; +struct ConstraintInfo { + ConstraintInfo() = default; + ConstraintInfo(SymbolID USR, StringRef Name) + : ConceptRef(USR, Name, InfoType::IT_concept) {} + Reference ConceptRef; + + SmallString<16> ConstraintExpr; +}; + // Records the template information for a struct or function that is a template // or an explicit template specialization. struct TemplateInfo { @@ -200,6 +231,7 @@ struct TemplateInfo { // Set when this is a specialization of another record/function. std::optional Specialization; + std::vector Constraints; }; // Info for field types. @@ -494,6 +526,17 @@ struct EnumInfo : public SymbolInfo { llvm::SmallVector Members; // List of enum members. }; +struct ConceptInfo : public SymbolInfo { + ConceptInfo() : SymbolInfo(InfoType::IT_concept) {} + ConceptInfo(SymbolID USR) : SymbolInfo(InfoType::IT_concept, USR) {} + + void merge(ConceptInfo &&I); + + bool IsType; + TemplateInfo Template; + SmallString<16> ConstraintExpression; +}; + struct Index : public Reference { Index() = default; Index(StringRef Name) : Reference(SymbolID(), Name) {} diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index 3932a939de97..5f3e5c37fa34 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -21,6 +21,17 @@ namespace clang { namespace doc { namespace serialize { +namespace { +static SmallString<16> exprToString(const clang::Expr *E) { + clang::LangOptions Opts; + clang::PrintingPolicy Policy(Opts); + SmallString<16> Result; + llvm::raw_svector_ostream OS(Result); + E->printPretty(OS, nullptr, Policy); + return Result; +} +} // namespace + SymbolID hashUSR(llvm::StringRef USR) { return llvm::SHA1::hash(arrayRefFromStringRef(USR)); } @@ -270,7 +281,7 @@ class ClangDocCommentVisitor }; void ClangDocCommentVisitor::parseComment(const comments::Comment *C) { - CurrentCI.Kind = C->getCommentKindName(); + CurrentCI.Kind = stringToCommentKind(C->getCommentKindName()); ConstCommentVisitor::visit(C); for (comments::Comment *Child : llvm::make_range(C->child_begin(), C->child_end())) { @@ -388,9 +399,13 @@ std::string serialize(std::unique_ptr &I) { return serialize(*static_cast(I.get())); case InfoType::IT_function: return serialize(*static_cast(I.get())); - default: + case InfoType::IT_concept: + return serialize(*static_cast(I.get())); + case InfoType::IT_typedef: + case InfoType::IT_default: return ""; } + llvm_unreachable("unhandled enumerator"); } static void parseFullComment(const FullComment *C, CommentInfo &CI) { @@ -489,6 +504,10 @@ static void InsertChild(ScopeChildren &Scope, TypedefInfo Info) { Scope.Typedefs.push_back(std::move(Info)); } +static void InsertChild(ScopeChildren &Scope, ConceptInfo Info) { + Scope.Concepts.push_back(std::move(Info)); +} + // Creates a parent of the correct type for the given child and inserts it into // that parent. // @@ -525,9 +544,14 @@ static std::unique_ptr makeAndInsertIntoParent(ChildType Child) { InsertChild(ParentRec->Children, std::forward(Child)); return ParentRec; } - default: - llvm_unreachable("Invalid reference type for parent namespace"); + case InfoType::IT_default: + case InfoType::IT_enum: + case InfoType::IT_function: + case InfoType::IT_typedef: + case InfoType::IT_concept: + break; } + llvm_unreachable("Invalid reference type for parent namespace"); } // There are two uses for this function. @@ -734,6 +758,50 @@ static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C, I.Loc.emplace_back(Loc); } +static void +handleCompoundConstraints(const Expr *Constraint, + std::vector &ConstraintInfos) { + if (Constraint->getStmtClass() == Stmt::ParenExprClass) { + handleCompoundConstraints(dyn_cast(Constraint)->getSubExpr(), + ConstraintInfos); + } else if (Constraint->getStmtClass() == Stmt::BinaryOperatorClass) { + auto *BinaryOpExpr = dyn_cast(Constraint); + handleCompoundConstraints(BinaryOpExpr->getLHS(), ConstraintInfos); + handleCompoundConstraints(BinaryOpExpr->getRHS(), ConstraintInfos); + } else if (Constraint->getStmtClass() == + Stmt::ConceptSpecializationExprClass) { + auto *Concept = dyn_cast(Constraint); + ConstraintInfo CI(getUSRForDecl(Concept->getNamedConcept()), + Concept->getNamedConcept()->getNameAsString()); + CI.ConstraintExpr = exprToString(Concept); + ConstraintInfos.push_back(CI); + } +} + +static void populateConstraints(TemplateInfo &I, const TemplateDecl *D) { + if (!D || !D->hasAssociatedConstraints()) + return; + + SmallVector AssociatedConstraints; + D->getAssociatedConstraints(AssociatedConstraints); + for (const auto &Constraint : AssociatedConstraints) { + if (!Constraint) + continue; + + // TODO: Investigate if atomic constraints need to be handled specifically. + if (const auto *ConstraintExpr = + dyn_cast_or_null( + Constraint.ConstraintExpr)) { + ConstraintInfo CI(getUSRForDecl(ConstraintExpr->getNamedConcept()), + ConstraintExpr->getNamedConcept()->getNameAsString()); + CI.ConstraintExpr = exprToString(ConstraintExpr); + I.Constraints.push_back(std::move(CI)); + } else { + handleCompoundConstraints(Constraint.ConstraintExpr, I.Constraints); + } + } +} + static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, const FullComment *FC, Location Loc, bool &IsInAnonymousNamespace) { @@ -742,8 +810,11 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, I.ReturnType = getTypeInfoForType(D->getReturnType(), LO); I.Prototype = getFunctionPrototype(D); parseParameters(I, D); + I.IsStatic = D->isStatic(); populateTemplateParameters(I.Template, D); + if (I.Template) + populateConstraints(I.Template.value(), D->getDescribedFunctionTemplate()); // Handle function template specializations. if (const FunctionTemplateSpecializationInfo *FTSI = @@ -896,6 +967,8 @@ emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc, RI->Path = getInfoRelativePath(RI->Namespace); populateTemplateParameters(RI->Template, D); + if (RI->Template) + populateConstraints(RI->Template.value(), D->getDescribedTemplate()); // Full and partial specializations. if (auto *CTSD = dyn_cast(D)) { @@ -1067,6 +1140,30 @@ emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc, return {nullptr, makeAndInsertIntoParent(std::move(Enum))}; } +std::pair, std::unique_ptr> +emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc, + bool PublicOnly) { + ConceptInfo Concept; + + bool IsInAnonymousNamespace = false; + populateInfo(Concept, D, FC, IsInAnonymousNamespace); + Concept.IsType = D->isTypeConcept(); + Concept.DefLoc = Loc; + Concept.ConstraintExpression = exprToString(D->getConstraintExpr()); + + if (auto *ConceptParams = D->getTemplateParameters()) { + for (const auto *Param : ConceptParams->asArray()) { + Concept.Template.Params.emplace_back( + getSourceCode(Param, Param->getSourceRange())); + } + } + + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + return {nullptr, makeAndInsertIntoParent(std::move(Concept))}; +} + } // namespace serialize } // namespace doc } // namespace clang diff --git a/clang-tools-extra/clang-doc/Serialize.h b/clang-tools-extra/clang-doc/Serialize.h index 7e6cbb70721e..497b09bb339f 100644 --- a/clang-tools-extra/clang-doc/Serialize.h +++ b/clang-tools-extra/clang-doc/Serialize.h @@ -68,6 +68,10 @@ std::pair, std::unique_ptr> emitInfo(const TypeAliasDecl *D, const FullComment *FC, Location Loc, bool PublicOnly); +std::pair, std::unique_ptr> +emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc, + bool PublicOnly); + // Function to hash a given USR value for storage. // As USRs (Unified Symbol Resolution) could be large, especially for functions // with long type arguments, we use 160-bits SHA1(USR) values to diff --git a/clang-tools-extra/clang-doc/YAMLGenerator.cpp b/clang-tools-extra/clang-doc/YAMLGenerator.cpp index 8c110b34e8e2..f95887104698 100644 --- a/clang-tools-extra/clang-doc/YAMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/YAMLGenerator.cpp @@ -65,6 +65,34 @@ template <> struct ScalarEnumerationTraits { } }; +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, clang::doc::CommentKind &Value) { + IO.enumCase(Value, "FullComment", clang::doc::CommentKind::CK_FullComment); + IO.enumCase(Value, "ParagraphComment", + clang::doc::CommentKind::CK_ParagraphComment); + IO.enumCase(Value, "TextComment", clang::doc::CommentKind::CK_TextComment); + IO.enumCase(Value, "InlineCommandComment", + clang::doc::CommentKind::CK_InlineCommandComment); + IO.enumCase(Value, "HTMLStartTagComment", + clang::doc::CommentKind::CK_HTMLStartTagComment); + IO.enumCase(Value, "HTMLEndTagComment", + clang::doc::CommentKind::CK_HTMLEndTagComment); + IO.enumCase(Value, "BlockCommandComment", + clang::doc::CommentKind::CK_BlockCommandComment); + IO.enumCase(Value, "ParamCommandComment", + clang::doc::CommentKind::CK_ParamCommandComment); + IO.enumCase(Value, "TParamCommandComment", + clang::doc::CommentKind::CK_TParamCommandComment); + IO.enumCase(Value, "VerbatimBlockComment", + clang::doc::CommentKind::CK_VerbatimBlockComment); + IO.enumCase(Value, "VerbatimBlockLineComment", + clang::doc::CommentKind::CK_VerbatimBlockLineComment); + IO.enumCase(Value, "VerbatimLineComment", + clang::doc::CommentKind::CK_VerbatimLineComment); + IO.enumCase(Value, "Unknown", clang::doc::CommentKind::CK_Unknown); + } +}; + // Scalars to YAML output. template struct ScalarTraits> { @@ -149,7 +177,7 @@ static void recordInfoMapping(IO &IO, RecordInfo &I) { } static void commentInfoMapping(IO &IO, CommentInfo &I) { - IO.mapOptional("Kind", I.Kind, SmallString<16>()); + IO.mapOptional("Kind", I.Kind, CommentKind::CK_Unknown); IO.mapOptional("Text", I.Text, SmallString<64>()); IO.mapOptional("Name", I.Name, SmallString<16>()); IO.mapOptional("Direction", I.Direction, SmallString<8>()); @@ -380,6 +408,8 @@ llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, case InfoType::IT_typedef: InfoYAML << *static_cast(I); break; + case InfoType::IT_concept: + break; case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected InfoType"); diff --git a/clang-tools-extra/clang-doc/assets/namespace-template.mustache b/clang-tools-extra/clang-doc/assets/namespace-template.mustache index 12dc93069d1c..1a44ed3c3ccc 100644 --- a/clang-tools-extra/clang-doc/assets/namespace-template.mustache +++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache @@ -23,23 +23,79 @@
- Content + {{#Enums}} +
+

Enumerations

+
+ {{#Obj}} + {{>EnumPartial}} + {{/Obj}} +
+
+ {{/Enums}} + {{#Record}} +
+

Inner Classes

+ +
+ {{/Record}}
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp index 0d2415c61921..3bb67baf6573 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -110,7 +110,7 @@ Turn on time profiler. Generates clang-doc-tracing.json)"), llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); -enum OutputFormatTy { md, yaml, html, mustache }; +enum OutputFormatTy { md, yaml, html, mustache, json }; static llvm::cl::opt FormatEnum( "format", llvm::cl::desc("Format for outputted docs."), @@ -121,7 +121,9 @@ static llvm::cl::opt FormatEnum( clEnumValN(OutputFormatTy::html, "html", "Documentation in HTML format."), clEnumValN(OutputFormatTy::mustache, "mustache", - "Documentation in mustache HTML format")), + "Documentation in mustache HTML format"), + clEnumValN(OutputFormatTy::json, "json", + "Documentation in JSON format")), llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory)); static llvm::ExitOnError ExitOnErr; @@ -136,6 +138,8 @@ static std::string getFormatString() { return "html"; case OutputFormatTy::mustache: return "mustache"; + case OutputFormatTy::json: + return "json"; } llvm_unreachable("Unknown OutputFormatTy"); } @@ -267,7 +271,8 @@ static llvm::Error handleMappingFailures(llvm::Error Err) { static llvm::Error createDirectories(llvm::StringRef OutDirectory) { if (std::error_code Err = llvm::sys::fs::create_directories(OutDirectory)) - return llvm::createFileError(OutDirectory, Err); + return llvm::createFileError(OutDirectory, Err, + "failed to create directory."); return llvm::Error::success(); } diff --git a/clang-tools-extra/clang-include-fixer/tool/ClangIncludeFixer.cpp b/clang-tools-extra/clang-include-fixer/tool/ClangIncludeFixer.cpp index 746ba7bcea01..9f73f4799805 100644 --- a/clang-tools-extra/clang-include-fixer/tool/ClangIncludeFixer.cpp +++ b/clang-tools-extra/clang-include-fixer/tool/ClangIncludeFixer.cpp @@ -167,13 +167,11 @@ createSymbolIndexManager(StringRef FilePath) { // Parse input and fill the database with it. // =
<, header...> // Multiple symbols can be given, separated by semicolons. - std::map> SymbolsMap; SmallVector SemicolonSplits; StringRef(Input).split(SemicolonSplits, ";"); std::vector Symbols; for (StringRef Pair : SemicolonSplits) { auto Split = Pair.split('='); - std::vector Headers; SmallVector CommaSplits; Split.second.split(CommaSplits, ","); for (size_t I = 0, E = CommaSplits.size(); I != E; ++I) diff --git a/clang-tools-extra/clang-move/Move.h b/clang-tools-extra/clang-move/Move.h index ea241bbbc4f8..ba613abc03e1 100644 --- a/clang-tools-extra/clang-move/Move.h +++ b/clang-tools-extra/clang-move/Move.h @@ -49,9 +49,7 @@ class DeclarationReporter { bool Templated = false; // Whether the declaration is templated. }; - const std::vector getDeclarationList() const { - return DeclarationList; - } + ArrayRef getDeclarationList() const { return DeclarationList; } private: std::vector DeclarationList; diff --git a/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp b/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp index ea0207619fb2..ada9122b587a 100644 --- a/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp +++ b/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp @@ -19,6 +19,8 @@ #include "clang/AST/Decl.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/Refactoring.h" #include "llvm/ADT/STLExtras.h" @@ -50,6 +52,85 @@ static const RecordDecl *findDefinition(StringRef RecordName, return selectFirst("recordDecl", Results); } +static bool declaresMultipleFieldsInStatement(const RecordDecl *Decl) { + SourceLocation LastTypeLoc; + for (const auto &Field : Decl->fields()) { + SourceLocation TypeLoc = + Field->getTypeSourceInfo()->getTypeLoc().getBeginLoc(); + if (LastTypeLoc.isValid() && TypeLoc == LastTypeLoc) + return true; + LastTypeLoc = TypeLoc; + } + return false; +} + +static bool declaresMultipleFieldsInMacro(const RecordDecl *Decl, + const SourceManager &SrcMgr) { + SourceLocation LastMacroLoc; + for (const auto &Field : Decl->fields()) { + if (!Field->getLocation().isMacroID()) + continue; + SourceLocation MacroLoc = SrcMgr.getExpansionLoc(Field->getLocation()); + if (LastMacroLoc.isValid() && MacroLoc == LastMacroLoc) + return true; + LastMacroLoc = MacroLoc; + } + return false; +} + +static bool containsPreprocessorDirectives(const RecordDecl *Decl, + const SourceManager &SrcMgr, + const LangOptions &LangOpts) { + std::pair FileAndOffset = + SrcMgr.getDecomposedLoc(Decl->field_begin()->getBeginLoc()); + assert(!Decl->field_empty()); + auto LastField = Decl->field_begin(); + while (std::next(LastField) != Decl->field_end()) + ++LastField; + unsigned EndOffset = SrcMgr.getFileOffset(LastField->getEndLoc()); + StringRef SrcBuffer = SrcMgr.getBufferData(FileAndOffset.first); + Lexer L(SrcMgr.getLocForStartOfFile(FileAndOffset.first), LangOpts, + SrcBuffer.data(), SrcBuffer.data() + FileAndOffset.second, + SrcBuffer.data() + SrcBuffer.size()); + IdentifierTable Identifiers(LangOpts); + clang::Token T; + while (!L.LexFromRawLexer(T) && L.getCurrentBufferOffset() < EndOffset) { + if (T.getKind() == tok::hash) { + L.LexFromRawLexer(T); + if (T.getKind() == tok::raw_identifier) { + clang::IdentifierInfo &II = Identifiers.get(T.getRawIdentifier()); + if (II.getPPKeywordID() != clang::tok::pp_not_keyword) + return true; + } + } + } + return false; +} + +static bool isSafeToRewrite(const RecordDecl *Decl, const ASTContext &Context) { + // All following checks expect at least one field declaration. + if (Decl->field_empty()) + return true; + + // Don't attempt to rewrite if there is a declaration like 'int a, b;'. + if (declaresMultipleFieldsInStatement(Decl)) + return false; + + const SourceManager &SrcMgr = Context.getSourceManager(); + + // Don't attempt to rewrite if a single macro expansion creates multiple + // fields. + if (declaresMultipleFieldsInMacro(Decl, SrcMgr)) + return false; + + // Prevent rewriting if there are preprocessor directives present between the + // start of the first field and the end of last field. + if (containsPreprocessorDirectives(Decl, SrcMgr, Context.getLangOpts())) + return false; + + return true; +} + /// Calculates the new order of fields. /// /// \returns empty vector if the list of fields doesn't match the definition. @@ -86,6 +167,10 @@ getNewFieldsOrder(const RecordDecl *Definition, static void addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context, std::map &Replacements) { + if (Old.getBegin().isMacroID()) + Old = Context.getSourceManager().getExpansionRange(Old).getAsRange(); + if (New.getBegin().isMacroID()) + New = Context.getSourceManager().getExpansionRange(New).getAsRange(); StringRef NewText = Lexer::getSourceText(CharSourceRange::getTokenRange(New), Context.getSourceManager(), Context.getLangOpts()); @@ -341,6 +426,8 @@ class ReorderingConsumer : public ASTConsumer { const RecordDecl *RD = findDefinition(RecordName, Context); if (!RD) return; + if (!isSafeToRewrite(RD, Context)) + return; SmallVector NewFieldsOrder = getNewFieldsOrder(RD, DesiredFieldsOrder); if (NewFieldsOrder.empty()) diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index 38d58bc13774..f4ab93b51f4a 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -419,8 +419,8 @@ ClangTidyASTConsumerFactory::createASTConsumer( std::unique_ptr Profiling; if (Context.getEnableProfiling()) { - Profiling = std::make_unique( - Context.getProfileStorageParams()); + Profiling = + std::make_unique(Context.getProfileStorageParams()); FinderOptions.CheckProfiling.emplace(Profiling->Records); } @@ -432,8 +432,8 @@ ClangTidyASTConsumerFactory::createASTConsumer( if (Context.canEnableModuleHeadersParsing() && Context.getLangOpts().Modules && OverlayFS != nullptr) { - auto ModuleExpander = std::make_unique( - &Compiler, OverlayFS); + auto ModuleExpander = + std::make_unique(&Compiler, OverlayFS); ModuleExpanderPP = ModuleExpander->getPreprocessor(); PP->addPPCallbacks(std::move(ModuleExpander)); } @@ -497,7 +497,7 @@ getCheckNames(const ClangTidyOptions &Options, bool AllowEnablingAnalyzerAlphaCheckers) { clang::tidy::ClangTidyContext Context( std::make_unique(ClangTidyGlobalOptions(), - Options), + Options), AllowEnablingAnalyzerAlphaCheckers); ClangTidyASTConsumerFactory Factory(Context); return Factory.getCheckNames(); @@ -508,7 +508,7 @@ getCheckOptions(const ClangTidyOptions &Options, bool AllowEnablingAnalyzerAlphaCheckers) { clang::tidy::ClangTidyContext Context( std::make_unique(ClangTidyGlobalOptions(), - Options), + Options), AllowEnablingAnalyzerAlphaCheckers); ClangTidyDiagnosticConsumer DiagConsumer(Context); auto DiagOpts = std::make_unique(); diff --git a/clang-tools-extra/clang-tidy/ClangTidyCheck.h b/clang-tools-extra/clang-tidy/ClangTidyCheck.h index 037526a0bd9a..399d45911549 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyCheck.h +++ b/clang-tools-extra/clang-tidy/ClangTidyCheck.h @@ -530,7 +530,6 @@ void ClangTidyCheck::OptionsView::store( ClangTidyOptions::OptionMap &Options, StringRef LocalName, bool Value) const; - } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h index bd7a1bf2c11c..a8851e794f24 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -217,11 +217,10 @@ class ClangTidyContext { using DiagLevelAndFormatString = std::pair; DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID, SourceLocation Loc) { - return { - static_cast( - DiagEngine->getDiagnosticLevel(DiagnosticID, Loc)), - std::string( - DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID))}; + return {static_cast( + DiagEngine->getDiagnosticLevel(DiagnosticID, Loc)), + std::string( + DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID))}; } void setOptionsCollector(llvm::StringSet<> *Collector) { diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp index 1c480d107ae9..e59f157b468b 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp @@ -70,7 +70,8 @@ struct NOptionMap { NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) { Options.reserve(OptionMap.size()); for (const auto &KeyValue : OptionMap) - Options.emplace_back(std::string(KeyValue.getKey()), KeyValue.getValue().Value); + Options.emplace_back(std::string(KeyValue.getKey()), + KeyValue.getValue().Value); } ClangTidyOptions::OptionMap denormalize(IO &) { ClangTidyOptions::OptionMap Map; diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h index dd78c570d25d..6ddc5f9b9cf9 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h @@ -204,7 +204,9 @@ class FileOptionsBaseProvider : public DefaultOptionsProvider { protected: // A pair of configuration file base name and a function parsing // configuration from text in the corresponding format. - using ConfigFileHandler = std::pair (llvm::MemoryBufferRef)>>; + using ConfigFileHandler = + std::pair( + llvm::MemoryBufferRef)>>; /// Configuration file handlers listed in the order of priority. /// diff --git a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp index 6a84704434c3..2c17cd3b6e97 100644 --- a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp +++ b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp @@ -49,8 +49,8 @@ class ExpandModularHeadersPPCallbacks::FileRecorder { FilesToRecord.erase(File); } - /// Makes sure we have contents for all the files we were interested in. Ideally - /// `FilesToRecord` should be empty. + /// Makes sure we have contents for all the files we were interested in. + /// Ideally `FilesToRecord` should be empty. void checkAllFilesRecorded() { LLVM_DEBUG({ for (auto FileEntry : FilesToRecord) diff --git a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h index c3478917ef49..e599bda92c25 100644 --- a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h +++ b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h @@ -35,10 +35,10 @@ namespace tooling { /// including the contents of the modular headers and all their transitive /// includes. /// -/// This allows existing tools based on PPCallbacks to retain their functionality -/// when running with C++ modules enabled. This only works in the backwards -/// compatible modules mode, i.e. when code can still be parsed in non-modular -/// way. +/// This allows existing tools based on PPCallbacks to retain their +/// functionality when running with C++ modules enabled. This only works in the +/// backwards compatible modules mode, i.e. when code can still be parsed in +/// non-modular way. class ExpandModularHeadersPPCallbacks : public PPCallbacks { public: ExpandModularHeadersPPCallbacks( diff --git a/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp b/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp index 09a09e6c599b..78605d59b442 100644 --- a/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/abseil/AbseilTidyModule.cpp @@ -59,14 +59,12 @@ class AbseilModule : public ClangTidyModule { CheckFactories.registerCheck("abseil-no-namespace"); CheckFactories.registerCheck( "abseil-redundant-strcat-calls"); - CheckFactories.registerCheck( - "abseil-str-cat-append"); + CheckFactories.registerCheck("abseil-str-cat-append"); CheckFactories.registerCheck( "abseil-string-find-startswith"); CheckFactories.registerCheck( "abseil-string-find-str-contains"); - CheckFactories.registerCheck( - "abseil-time-comparison"); + CheckFactories.registerCheck("abseil-time-comparison"); CheckFactories.registerCheck( "abseil-time-subtraction"); CheckFactories.registerCheck( @@ -82,6 +80,6 @@ static ClangTidyModuleRegistry::Add X("abseil-module", // This anchor is used to force the linker to link in the generated object file // and thus register the AbseilModule. -volatile int AbseilModuleAnchorSource = 0; +volatile int AbseilModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp b/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp index 0819642be055..8063fc540cce 100644 --- a/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/CleanupCtadCheck.cpp @@ -18,7 +18,7 @@ using namespace ::clang::transformer; namespace clang::tidy::abseil { -RewriteRuleWith cleanupCtadCheckImpl() { +static RewriteRuleWith cleanupCtadCheckImpl() { auto WarningMessage = cat("prefer absl::Cleanup's class template argument " "deduction pattern in C++17 and higher"); diff --git a/clang-tools-extra/clang-tidy/abseil/DurationDivisionCheck.h b/clang-tools-extra/clang-tidy/abseil/DurationDivisionCheck.h index 6b194e3d2664..c8f259521b64 100644 --- a/clang-tools-extra/clang-tidy/abseil/DurationDivisionCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/DurationDivisionCheck.h @@ -15,7 +15,7 @@ namespace clang::tidy::abseil { // Find potential incorrect uses of integer division of absl::Duration objects. // -// For the user-facing documentation see: +// For the user-facing documentation see: // http://clang.llvm.org/extra/clang-tidy/checks/abseil/duration-division.html class DurationDivisionCheck : public ClangTidyCheck { diff --git a/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.cpp b/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.cpp index edaa584a4d8e..19409d0616f0 100644 --- a/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.cpp @@ -27,7 +27,8 @@ void NoInternalDependenciesCheck::registerMatchers(MatchFinder *Finder) { this); } -void NoInternalDependenciesCheck::check(const MatchFinder::MatchResult &Result) { +void NoInternalDependenciesCheck::check( + const MatchFinder::MatchResult &Result) { const auto *InternalDependency = Result.Nodes.getNodeAs("InternalDep"); diff --git a/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.h b/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.h index dfaa2cbf8f90..9c16524ff6b5 100644 --- a/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/NoInternalDependenciesCheck.h @@ -1,4 +1,4 @@ -//===--- NoInternalDependenciesCheck.h - clang-tidy----------------------*- C++ -*-===// +//===--- NoInternalDependenciesCheck.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. diff --git a/clang-tools-extra/clang-tidy/abseil/NoNamespaceCheck.cpp b/clang-tools-extra/clang-tidy/abseil/NoNamespaceCheck.cpp index b45a9c99d780..6c2baa4c4141 100644 --- a/clang-tools-extra/clang-tidy/abseil/NoNamespaceCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/NoNamespaceCheck.cpp @@ -15,10 +15,9 @@ using namespace clang::ast_matchers; namespace clang::tidy::abseil { void NoNamespaceCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher( - namespaceDecl(hasName("::absl"), unless(isInAbseilFile())) - .bind("abslNamespace"), - this); + Finder->addMatcher(namespaceDecl(hasName("::absl"), unless(isInAbseilFile())) + .bind("abslNamespace"), + this); } void NoNamespaceCheck::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp index 04ca00614350..ef26a8a76cb3 100644 --- a/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.cpp @@ -23,7 +23,7 @@ namespace clang::tidy::abseil { // - Make it work in macros if the outer and inner StrCats are both in the // argument. -void RedundantStrcatCallsCheck::registerMatchers(MatchFinder* Finder) { +void RedundantStrcatCallsCheck::registerMatchers(MatchFinder *Finder) { const auto CallToStrcat = callExpr(callee(functionDecl(hasName("::absl::StrCat")))); const auto CallToStrappend = @@ -62,7 +62,7 @@ const clang::CallExpr *processArgument(const Expr *Arg, const MatchFinder::MatchResult &Result, StrCatCheckResult *CheckResult) { const auto IsAlphanum = hasDeclaration(cxxMethodDecl(hasName("AlphaNum"))); - static const auto* const Strcat = new auto(hasName("::absl::StrCat")); + static const auto *const Strcat = new auto(hasName("::absl::StrCat")); const auto IsStrcat = cxxBindTemporaryExpr( has(callExpr(callee(functionDecl(*Strcat))).bind("StrCat"))); if (const auto *SubStrcatCall = selectFirst( @@ -81,18 +81,18 @@ const clang::CallExpr *processArgument(const Expr *Arg, StrCatCheckResult processCall(const CallExpr *RootCall, bool IsAppend, const MatchFinder::MatchResult &Result) { StrCatCheckResult CheckResult; - std::deque CallsToProcess = {RootCall}; + std::deque CallsToProcess = {RootCall}; while (!CallsToProcess.empty()) { ++CheckResult.NumCalls; - const CallExpr* CallExpr = CallsToProcess.front(); + const CallExpr *CallExpr = CallsToProcess.front(); CallsToProcess.pop_front(); int StartArg = CallExpr == RootCall && IsAppend; for (const auto *Arg : CallExpr->arguments()) { - if (StartArg-- > 0) - continue; + if (StartArg-- > 0) + continue; if (const clang::CallExpr *Sub = processArgument(Arg, Result, &CheckResult)) { CallsToProcess.push_back(Sub); @@ -101,18 +101,18 @@ StrCatCheckResult processCall(const CallExpr *RootCall, bool IsAppend, } return CheckResult; } -} // namespace +} // namespace -void RedundantStrcatCallsCheck::check(const MatchFinder::MatchResult& Result) { +void RedundantStrcatCallsCheck::check(const MatchFinder::MatchResult &Result) { bool IsAppend = false; const CallExpr *RootCall = nullptr; - if ((RootCall = Result.Nodes.getNodeAs("StrCat"))) - IsAppend = false; - else if ((RootCall = Result.Nodes.getNodeAs("StrAppend"))) - IsAppend = true; - else - return; + if ((RootCall = Result.Nodes.getNodeAs("StrCat"))) + IsAppend = false; + else if ((RootCall = Result.Nodes.getNodeAs("StrAppend"))) + IsAppend = true; + else + return; if (RootCall->getBeginLoc().isMacroID()) { // Ignore calls within macros. @@ -128,8 +128,8 @@ void RedundantStrcatCallsCheck::check(const MatchFinder::MatchResult& Result) { return; } - diag(RootCall->getBeginLoc(), - "multiple calls to 'absl::StrCat' can be flattened into a single call") + diag(RootCall->getBeginLoc(), + "multiple calls to 'absl::StrCat' can be flattened into a single call") << CheckResult.Hints; } diff --git a/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.h b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.h index 04c7c9cb4b78..a27899d3e821 100644 --- a/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/RedundantStrcatCallsCheck.h @@ -13,8 +13,8 @@ namespace clang::tidy::abseil { -/// Flags redundant calls to absl::StrCat when the result is being passed to -/// another call of absl::StrCat/absl::StrAppend. Also suggests a fix to +/// Flags redundant calls to absl::StrCat when the result is being passed to +/// another call of absl::StrCat/absl::StrAppend. Also suggests a fix to /// collapse the calls. /// Example: /// StrCat(1, StrCat(2, 3)) ==> StrCat(1, 2, 3) diff --git a/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp b/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp index ab6ed701e59f..ced92590be02 100644 --- a/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.cpp @@ -17,7 +17,7 @@ namespace clang::tidy::abseil { namespace { // Skips any combination of temporary materialization, temporary binding and // implicit casting. -AST_MATCHER_P(Stmt, IgnoringTemporaries, ast_matchers::internal::Matcher, +AST_MATCHER_P(Stmt, ignoringTemporaries, ast_matchers::internal::Matcher, InnerMatcher) { const Stmt *E = &Node; while (true) { @@ -34,16 +34,16 @@ AST_MATCHER_P(Stmt, IgnoringTemporaries, ast_matchers::internal::Matcher, return InnerMatcher.matches(*E, Finder, Builder); } -} // namespace +} // namespace // TODO: str += StrCat(...) // str.append(StrCat(...)) void StrCatAppendCheck::registerMatchers(MatchFinder *Finder) { const auto StrCat = functionDecl(hasName("::absl::StrCat")); - // The arguments of absl::StrCat are implicitly converted to AlphaNum. This - // matches to the arguments because of that behavior. - const auto AlphaNum = IgnoringTemporaries(cxxConstructExpr( + // The arguments of absl::StrCat are implicitly converted to AlphaNum. This + // matches to the arguments because of that behavior. + const auto AlphaNum = ignoringTemporaries(cxxConstructExpr( argumentCountIs(1), hasType(cxxRecordDecl(hasName("::absl::AlphaNum"))), hasArgument(0, ignoringImpCasts(declRefExpr(to(equalsBoundNode("LHS")), expr().bind("Arg0")))))); @@ -62,7 +62,7 @@ void StrCatAppendCheck::registerMatchers(MatchFinder *Finder) { hasOverloadedOperatorName("="), hasArgument(0, declRefExpr(to(decl().bind("LHS")))), hasArgument( - 1, IgnoringTemporaries( + 1, ignoringTemporaries( callExpr(callee(StrCat), hasArgument(0, AlphaNum), unless(HasAnotherReferenceToLhs)) .bind("Call")))) @@ -73,7 +73,7 @@ void StrCatAppendCheck::registerMatchers(MatchFinder *Finder) { void StrCatAppendCheck::check(const MatchFinder::MatchResult &Result) { const auto *Op = Result.Nodes.getNodeAs("Op"); const auto *Call = Result.Nodes.getNodeAs("Call"); - assert(Op != nullptr && Call != nullptr && "Matcher does not work as expected"); + assert(Op && Call && "Matcher does not work as expected"); // Handles the case 'x = absl::StrCat(x)', which has no effect. if (Call->getNumArgs() == 1) { diff --git a/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.h b/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.h index 22b55abfd83b..fcd9d4b6e188 100644 --- a/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/StrCatAppendCheck.h @@ -14,7 +14,7 @@ namespace clang::tidy::abseil { /// Flags uses of absl::StrCat to append to a string. Suggests absl::StrAppend -/// should be used instead. +/// should be used instead. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/abseil/str-cat-append.html diff --git a/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp b/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp index 6e89783bb51e..0c2fe285ce06 100644 --- a/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp +++ b/clang-tools-extra/clang-tidy/abseil/StringFindStrContainsCheck.cpp @@ -29,7 +29,9 @@ using ::clang::transformer::makeRule; using ::clang::transformer::node; using ::clang::transformer::RewriteRuleWith; +namespace { AST_MATCHER(Type, isCharType) { return Node.isCharType(); } +} // namespace static const char DefaultStringLikeClasses[] = "::std::basic_string;" "::std::basic_string_view;" diff --git a/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h b/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h index 7e7dc7351181..c947f6bca7f3 100644 --- a/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/TimeSubtractionCheck.h @@ -26,7 +26,7 @@ class TimeSubtractionCheck : public ClangTidyCheck { void check(const ast_matchers::MatchFinder::MatchResult &Result) override; private: - void emitDiagnostic(const Expr* Node, llvm::StringRef Replacement); + void emitDiagnostic(const Expr *Node, llvm::StringRef Replacement); }; } // namespace clang::tidy::abseil diff --git a/clang-tools-extra/clang-tidy/abseil/UpgradeDurationConversionsCheck.h b/clang-tools-extra/clang-tidy/abseil/UpgradeDurationConversionsCheck.h index beb5479aa6aa..8e7d9829533d 100644 --- a/clang-tools-extra/clang-tidy/abseil/UpgradeDurationConversionsCheck.h +++ b/clang-tools-extra/clang-tidy/abseil/UpgradeDurationConversionsCheck.h @@ -16,7 +16,8 @@ namespace clang::tidy::abseil { -/// Finds deprecated uses of `absl::Duration` arithmetic operators and factories. +/// Finds deprecated uses of `absl::Duration` arithmetic operators and +/// factories. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/abseil/upgrade-duration-conversions.html diff --git a/clang-tools-extra/clang-tidy/altera/AlteraTidyModule.cpp b/clang-tools-extra/clang-tidy/altera/AlteraTidyModule.cpp index 21610c7d1ece..02a43ba86d7b 100644 --- a/clang-tools-extra/clang-tidy/altera/AlteraTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/altera/AlteraTidyModule.cpp @@ -43,6 +43,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the AlteraModule. -volatile int AlteraModuleAnchorSource = 0; +volatile int AlteraModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/altera/SingleWorkItemBarrierCheck.cpp b/clang-tools-extra/clang-tidy/altera/SingleWorkItemBarrierCheck.cpp index c5da66a1f28b..c21b7cab1b8d 100644 --- a/clang-tools-extra/clang-tidy/altera/SingleWorkItemBarrierCheck.cpp +++ b/clang-tools-extra/clang-tidy/altera/SingleWorkItemBarrierCheck.cpp @@ -16,14 +16,14 @@ namespace clang::tidy::altera { void SingleWorkItemBarrierCheck::registerMatchers(MatchFinder *Finder) { // Find any function that calls barrier but does not call an ID function. - // hasAttr(attr::Kind::OpenCLKernel) restricts it to only kernel functions. + // hasAttr(attr::Kind::DeviceKernel) restricts it to only kernel functions. // FIXME: Have it accept all functions but check for a parameter that gets an // ID from one of the four ID functions. Finder->addMatcher( // Find function declarations... functionDecl( - // That are OpenCL kernels... - hasAttr(attr::Kind::OpenCLKernel), + // That are device kernels... + hasAttr(attr::Kind::DeviceKernel), // And call a barrier function (either 1.x or 2.x version)... forEachDescendant(callExpr(callee(functionDecl(hasAnyName( "barrier", "work_group_barrier")))) diff --git a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp index cdaedd440959..789327b196ab 100644 --- a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp +++ b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp @@ -47,7 +47,7 @@ void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { // Do not trigger on templated struct declarations because the packing and // alignment requirements are unknown. if (Struct->isTemplated()) - return; + return; // Packing and alignment requirements for invalid decls are meaningless. if (Struct->isInvalidDecl()) diff --git a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.h b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.h index 5edfd40ef77b..f360bcef4f14 100644 --- a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.h +++ b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.h @@ -22,7 +22,7 @@ class StructPackAlignCheck : public ClangTidyCheck { public: StructPackAlignCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), - MaxConfiguredAlignment(Options.get("MaxConfiguredAlignment", 128)) {} + MaxConfiguredAlignment(Options.get("MaxConfiguredAlignment", 128)) {} 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/altera/UnrollLoopsCheck.cpp b/clang-tools-extra/clang-tidy/altera/UnrollLoopsCheck.cpp index 532a43bad741..0bb9d6e4a7ce 100644 --- a/clang-tools-extra/clang-tidy/altera/UnrollLoopsCheck.cpp +++ b/clang-tools-extra/clang-tidy/altera/UnrollLoopsCheck.cpp @@ -247,8 +247,8 @@ bool UnrollLoopsCheck::extractValue(int &Value, const BinaryOperator *Op, return true; } -bool UnrollLoopsCheck::exprHasLargeNumIterations(const Expr *Expression, - const ASTContext *Context) const { +bool UnrollLoopsCheck::exprHasLargeNumIterations( + const Expr *Expression, const ASTContext *Context) const { Expr::EvalResult Result; if (Expression->EvaluateAsRValue(Result, *Context)) { if (!Result.Val.isInt()) diff --git a/clang-tools-extra/clang-tidy/android/AndroidTidyModule.cpp b/clang-tools-extra/clang-tidy/android/AndroidTidyModule.cpp index 4541e926e886..17efa10909d0 100644 --- a/clang-tools-extra/clang-tidy/android/AndroidTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/android/AndroidTidyModule.cpp @@ -34,7 +34,8 @@ namespace android { class AndroidModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { - CheckFactories.registerCheck("android-cloexec-accept4"); + CheckFactories.registerCheck( + "android-cloexec-accept4"); CheckFactories.registerCheck("android-cloexec-accept"); CheckFactories.registerCheck("android-cloexec-creat"); CheckFactories.registerCheck("android-cloexec-dup"); @@ -66,6 +67,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the AndroidModule. -volatile int AndroidModuleAnchorSource = 0; +volatile int AndroidModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp index 8c34ecd6945c..c90fc7ba1bb0 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecAcceptCheck.cpp @@ -26,11 +26,10 @@ void CloexecAcceptCheck::registerMatchers(MatchFinder *Finder) { } void CloexecAcceptCheck::check(const MatchFinder::MatchResult &Result) { - std::string ReplacementText = - (Twine("accept4(") + getSpellingArg(Result, 0) + ", " + - getSpellingArg(Result, 1) + ", " + getSpellingArg(Result, 2) + - ", SOCK_CLOEXEC)") - .str(); + std::string ReplacementText = (Twine("accept4(") + getSpellingArg(Result, 0) + + ", " + getSpellingArg(Result, 1) + ", " + + getSpellingArg(Result, 2) + ", SOCK_CLOEXEC)") + .str(); replaceFunc( Result, diff --git a/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp index 39a0d74f3836..f4d657a7f4e9 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecCheck.cpp @@ -36,7 +36,7 @@ std::string buildFixMsgForStringFlag(const Expr *Arg, const SourceManager &SM, const char *CloexecCheck::FuncDeclBindingStr = "funcDecl"; -const char *CloexecCheck::FuncBindingStr ="func"; +const char *CloexecCheck::FuncBindingStr = "func"; void CloexecCheck::registerMatchersImpl( MatchFinder *Finder, internal::Matcher Function) { diff --git a/clang-tools-extra/clang-tidy/android/CloexecCreatCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecCreatCheck.cpp index ee270209e4fa..8d9f45c3567f 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecCreatCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecCreatCheck.cpp @@ -16,11 +16,10 @@ namespace clang::tidy::android { void CloexecCreatCheck::registerMatchers(MatchFinder *Finder) { auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); auto MODETType = hasType(namedDecl(hasName("mode_t"))); - registerMatchersImpl(Finder, - functionDecl(isExternC(), returns(isInteger()), - hasName("creat"), - hasParameter(0, CharPointerType), - hasParameter(1, MODETType))); + registerMatchersImpl(Finder, functionDecl(isExternC(), returns(isInteger()), + hasName("creat"), + hasParameter(0, CharPointerType), + hasParameter(1, MODETType))); } void CloexecCreatCheck::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/android/CloexecFopenCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecFopenCheck.cpp index 153e7af82d15..bb9d0d2cb3da 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecFopenCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecFopenCheck.cpp @@ -15,11 +15,10 @@ namespace clang::tidy::android { void CloexecFopenCheck::registerMatchers(MatchFinder *Finder) { auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); - registerMatchersImpl(Finder, - functionDecl(isExternC(), returns(asString("FILE *")), - hasName("fopen"), - hasParameter(0, CharPointerType), - hasParameter(1, CharPointerType))); + registerMatchersImpl( + Finder, functionDecl(isExternC(), returns(asString("FILE *")), + hasName("fopen"), hasParameter(0, CharPointerType), + hasParameter(1, CharPointerType))); } void CloexecFopenCheck::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/android/CloexecOpenCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecOpenCheck.cpp index 11807f95def4..623b6ab02e7b 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecOpenCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecOpenCheck.cpp @@ -20,12 +20,11 @@ void CloexecOpenCheck::registerMatchers(MatchFinder *Finder) { hasAnyName("open", "open64"), hasParameter(0, CharPointerType), hasParameter(1, hasType(isInteger())))); - registerMatchersImpl(Finder, - functionDecl(isExternC(), returns(isInteger()), - hasName("openat"), - hasParameter(0, hasType(isInteger())), - hasParameter(1, CharPointerType), - hasParameter(2, hasType(isInteger())))); + registerMatchersImpl( + Finder, functionDecl(isExternC(), returns(isInteger()), hasName("openat"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, CharPointerType), + hasParameter(2, hasType(isInteger())))); } void CloexecOpenCheck::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/android/CloexecPipe2Check.cpp b/clang-tools-extra/clang-tidy/android/CloexecPipe2Check.cpp index 2798c80146e9..e32332bdfc95 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecPipe2Check.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecPipe2Check.cpp @@ -14,10 +14,10 @@ using namespace clang::ast_matchers; namespace clang::tidy::android { void CloexecPipe2Check::registerMatchers(MatchFinder *Finder) { - registerMatchersImpl(Finder, - functionDecl(returns(isInteger()), hasName("pipe2"), - hasParameter(0, hasType(pointsTo(isInteger()))), - hasParameter(1, hasType(isInteger())))); + registerMatchersImpl( + Finder, functionDecl(returns(isInteger()), hasName("pipe2"), + hasParameter(0, hasType(pointsTo(isInteger()))), + hasParameter(1, hasType(isInteger())))); } void CloexecPipe2Check::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/android/CloexecPipeCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecPipeCheck.cpp index 9085be67b755..c59b127dc87a 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecPipeCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecPipeCheck.cpp @@ -14,19 +14,19 @@ using namespace clang::ast_matchers; namespace clang::tidy::android { void CloexecPipeCheck::registerMatchers(MatchFinder *Finder) { - registerMatchersImpl(Finder, - functionDecl(returns(isInteger()), hasName("pipe"), - hasParameter(0, hasType(pointsTo(isInteger()))))); + registerMatchersImpl( + Finder, functionDecl(returns(isInteger()), hasName("pipe"), + hasParameter(0, hasType(pointsTo(isInteger()))))); } void CloexecPipeCheck::check(const MatchFinder::MatchResult &Result) { std::string ReplacementText = (Twine("pipe2(") + getSpellingArg(Result, 0) + ", O_CLOEXEC)").str(); - replaceFunc( - Result, - "prefer pipe2() with O_CLOEXEC to avoid leaking file descriptors to child processes", - ReplacementText); + replaceFunc(Result, + "prefer pipe2() with O_CLOEXEC to avoid leaking file descriptors " + "to child processes", + ReplacementText); } } // namespace clang::tidy::android diff --git a/clang-tools-extra/clang-tidy/android/CloexecSocketCheck.cpp b/clang-tools-extra/clang-tidy/android/CloexecSocketCheck.cpp index 65c6a82036cc..12b31a050c2c 100644 --- a/clang-tools-extra/clang-tidy/android/CloexecSocketCheck.cpp +++ b/clang-tools-extra/clang-tidy/android/CloexecSocketCheck.cpp @@ -14,12 +14,11 @@ using namespace clang::ast_matchers; namespace clang::tidy::android { void CloexecSocketCheck::registerMatchers(MatchFinder *Finder) { - registerMatchersImpl(Finder, - functionDecl(isExternC(), returns(isInteger()), - hasName("socket"), - hasParameter(0, hasType(isInteger())), - hasParameter(1, hasType(isInteger())), - hasParameter(2, hasType(isInteger())))); + registerMatchersImpl( + Finder, functionDecl(isExternC(), returns(isInteger()), hasName("socket"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, hasType(isInteger())), + hasParameter(2, hasType(isInteger())))); } void CloexecSocketCheck::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp b/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp index 79d0e380e402..f414fe750d02 100644 --- a/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp @@ -32,6 +32,6 @@ static ClangTidyModuleRegistry::Add X("boost-module", // This anchor is used to force the linker to link in the generated object file // and thus register the BoostModule. -volatile int BoostModuleAnchorSource = 0; +volatile int BoostModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp index 0b76cfb2ad8d..a6cd68edda55 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp @@ -62,6 +62,7 @@ static bool isFallthroughSwitchBranch(const SwitchBranch &Branch) { return true; // Ignore sub-switches } + // NOLINTNEXTLINE(readability-identifier-naming) - FIXME bool TraverseSwitchCase(SwitchCase *, DataRecursionQueue * = nullptr) { return true; // Ignore cases } diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 64f4a524daf0..ed1fd138d8f1 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -291,6 +291,7 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the BugproneModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int BugproneModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp index c6bb5bdf5ce9..7378f1a24ffd 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp @@ -34,8 +34,7 @@ AST_MATCHER(BinaryOperator, AST_MATCHER(CXXOperatorCallExpr, hasCppOperatorAChildComparisonOperatorWithoutParen) { - return std::any_of(Node.arg_begin(), Node.arg_end(), - isExprAComparisonOperator); + return llvm::any_of(Node.arguments(), isExprAComparisonOperator); } struct ChainedComparisonData { diff --git a/clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.cpp index 93f35cb2c1a3..3fe028b94771 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.cpp @@ -15,6 +15,8 @@ using namespace clang::ast_matchers; namespace clang::tidy::bugprone { +namespace { + AST_MATCHER(clang::VarDecl, hasConstantDeclaration) { const Expr *Init = Node.getInit(); if (Init && !Init->isValueDependent()) { @@ -25,6 +27,8 @@ AST_MATCHER(clang::VarDecl, hasConstantDeclaration) { return false; } +} // namespace + DynamicStaticInitializersCheck::DynamicStaticInitializersCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), @@ -36,16 +40,18 @@ void DynamicStaticInitializersCheck::registerMatchers(MatchFinder *Finder) { this); } -void DynamicStaticInitializersCheck::check(const MatchFinder::MatchResult &Result) { +void DynamicStaticInitializersCheck::check( + const MatchFinder::MatchResult &Result) { const auto *Var = Result.Nodes.getNodeAs("var"); SourceLocation Loc = Var->getLocation(); - if (!Loc.isValid() || !utils::isPresumedLocInHeaderFile(Loc, *Result.SourceManager, - HeaderFileExtensions)) + if (!Loc.isValid() || !utils::isPresumedLocInHeaderFile( + Loc, *Result.SourceManager, HeaderFileExtensions)) return; // If the initializer is a constant expression, then the compiler // doesn't have to dynamically initialize it. - diag(Loc, "static variable %0 may be dynamically initialized in this header file") - << Var; + diag(Loc, + "static variable %0 may be dynamically initialized in this header file") + << Var; } } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp index 10868129e76d..a179d4bf66b4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp @@ -1497,11 +1497,13 @@ static MixableParameterRange modelMixingRange( } // namespace model +namespace { /// Matches DeclRefExprs and their ignorable wrappers to ParmVarDecls. AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher, paramRefExpr) { return expr(ignoringParenImpCasts(ignoringElidableConstructorCall( declRefExpr(to(parmVarDecl().bind("param")))))); } +} // namespace namespace filter { @@ -1574,8 +1576,8 @@ using ParamToSmallSetMap = /// Returns whether the sets mapped to the two elements in the map have at /// least one element in common. template -bool lazyMapOfSetsIntersectionExists(const MapTy &Map, const ElemTy &E1, - const ElemTy &E2) { +static bool lazyMapOfSetsIntersectionExists(const MapTy &Map, const ElemTy &E1, + const ElemTy &E2) { auto E1Iterator = Map.find(E1); auto E2Iterator = Map.find(E2); if (E1Iterator == Map.end() || E2Iterator == Map.end()) @@ -1882,6 +1884,8 @@ static bool prefixSuffixCoverUnderThreshold(std::size_t Threshold, } // namespace filter +namespace { + /// Matches functions that have at least the specified amount of parameters. AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N) { return Node.getNumParams() >= N; @@ -1904,6 +1908,8 @@ AST_MATCHER(FunctionDecl, isOverloadedUnaryOrBinaryOperator) { } } +} // namespace + /// Returns the DefaultMinimumLength if the Value of requested minimum length /// is less than 2. Minimum lengths of 0 or 1 are not accepted. static inline unsigned clampMinimumLength(const unsigned Value) { diff --git a/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp index 7626bf89e694..92425890a0ea 100644 --- a/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/InaccurateEraseCheck.cpp @@ -18,9 +18,9 @@ void InaccurateEraseCheck::registerMatchers(MatchFinder *Finder) { const auto EndCall = callExpr( callee(functionDecl(hasAnyName("remove", "remove_if", "unique"))), - hasArgument( - 1, optionally(cxxMemberCallExpr(callee(cxxMethodDecl(hasName("end")))) - .bind("end")))) + hasArgument(1, optionally(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("end")))) + .bind("end")))) .bind("alg"); const auto DeclInStd = type(hasUnqualifiedDesugaredType( @@ -35,10 +35,8 @@ void InaccurateEraseCheck::registerMatchers(MatchFinder *Finder) { } void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) { - const auto *MemberCall = - Result.Nodes.getNodeAs("erase"); - const auto *EndExpr = - Result.Nodes.getNodeAs("end"); + const auto *MemberCall = Result.Nodes.getNodeAs("erase"); + const auto *EndExpr = Result.Nodes.getNodeAs("end"); const SourceLocation Loc = MemberCall->getBeginLoc(); FixItHint Hint; diff --git a/clang-tools-extra/clang-tidy/bugprone/IncDecInConditionsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/IncDecInConditionsCheck.cpp index 9b3b01eb0268..73bffe93146e 100644 --- a/clang-tools-extra/clang-tidy/bugprone/IncDecInConditionsCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/IncDecInConditionsCheck.cpp @@ -15,6 +15,8 @@ using namespace clang::ast_matchers; namespace clang::tidy::bugprone { +namespace { + AST_MATCHER(BinaryOperator, isLogicalOperator) { return Node.isLogicalOp(); } AST_MATCHER(UnaryOperator, isUnaryPrePostOperator) { @@ -26,6 +28,8 @@ AST_MATCHER(CXXOperatorCallExpr, isPrePostOperator) { Node.getOperator() == OO_MinusMinus; } +} // namespace + void IncDecInConditionsCheck::registerMatchers(MatchFinder *Finder) { auto OperatorMatcher = expr( anyOf(binaryOperator(anyOf(isComparisonOperator(), isLogicalOperator())), diff --git a/clang-tools-extra/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp index 2a8d36215cc7..c2b0732a3e7b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp @@ -1,4 +1,4 @@ -//===--- IncorrectRoundingsCheck.cpp - clang-tidy ------------------------------===// +//===--- IncorrectRoundingsCheck.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. diff --git a/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp index b7f0c08b2a7d..07116a7ff15f 100644 --- a/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp @@ -83,8 +83,8 @@ static bool isVarThatIsPossiblyChanged(const Decl *Func, const Stmt *LoopStmt, isChanged(LoopStmt, Var, Context); // FIXME: Track references. } - } else if (isa(Cond)) { + } else if (isa(Cond)) { // FIXME: Handle MemberExpr. return true; } else if (const auto *CE = dyn_cast(Cond)) { @@ -274,8 +274,7 @@ static bool hasRecursionOverStaticLoopCondVariables(const Expr *Cond, void InfiniteLoopCheck::registerMatchers(MatchFinder *Finder) { const auto LoopCondition = allOf( - hasCondition( - expr(forCallable(decl().bind("func"))).bind("condition")), + hasCondition(expr(forCallable(decl().bind("func"))).bind("condition")), unless(hasBody(hasDescendant( loopEndingStmt(forCallable(equalsBoundNode("func"))))))); @@ -324,7 +323,7 @@ void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) { diag(LoopStmt->getBeginLoc(), "this loop is infinite; none of its condition variables (%0)" " are updated in the loop body") - << CondVarNames; + << CondVarNames; } } diff --git a/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.cpp index 32f5edddfe80..6d5c470616d1 100644 --- a/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.cpp @@ -34,12 +34,11 @@ class MacroExpansionsWithFileAndLine : public PPCallbacks { LambdaFunctionNameCheck::SourceRangeSet *SME) : SuppressMacroExpansions(SME) {} - void MacroExpands(const Token &MacroNameTok, - const MacroDefinition &MD, SourceRange Range, - const MacroArgs *Args) override { + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override { bool HasFile = false; bool HasLine = false; - for (const auto& T : MD.getMacroInfo()->tokens()) { + for (const Token &T : MD.getMacroInfo()->tokens()) { if (T.is(tok::identifier)) { StringRef IdentName = T.getIdentifierInfo()->getName(); if (IdentName == "__FILE__") { @@ -55,7 +54,7 @@ class MacroExpansionsWithFileAndLine : public PPCallbacks { } private: - LambdaFunctionNameCheck::SourceRangeSet* SuppressMacroExpansions; + LambdaFunctionNameCheck::SourceRangeSet *SuppressMacroExpansions; }; AST_MATCHER(CXXMethodDecl, isInLambda) { return Node.getParent()->isLambda(); } diff --git a/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp index 40e4ab6c8b12..5b166b4b3a9b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp @@ -74,7 +74,7 @@ void MisplacedOperatorInStrlenInAllocCheck::check( if (!Alloc) Alloc = Result.Nodes.getNodeAs("Alloc"); assert(Alloc && "Matched node bound by 'Alloc' should be either 'CallExpr'" - " or 'CXXNewExpr'"); + " or 'CXXNewExpr'"); const auto *StrLen = Result.Nodes.getNodeAs("StrLen"); const auto *BinOp = Result.Nodes.getNodeAs("BinOp"); diff --git a/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.h b/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.h index 3a09ddb4a6c0..93cf50d0b1c6 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.h @@ -21,7 +21,8 @@ namespace clang::tidy::bugprone { /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/misplaced-operator-in-strlen-in-alloc.html class MisplacedOperatorInStrlenInAllocCheck : public ClangTidyCheck { public: - MisplacedOperatorInStrlenInAllocCheck(StringRef Name, ClangTidyContext *Context) + MisplacedOperatorInStrlenInAllocCheck(StringRef Name, + ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} 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/MisplacedPointerArithmeticInAllocCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MisplacedPointerArithmeticInAllocCheck.cpp index b9129fa0811c..86785d36696d 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MisplacedPointerArithmeticInAllocCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/MisplacedPointerArithmeticInAllocCheck.cpp @@ -76,8 +76,8 @@ void MisplacedPointerArithmeticInAllocCheck::check( } else { const auto *CtrE = New->getConstructExpr(); if (!CtrE || !CtrE->getArg(CtrE->getNumArgs() - 1) - ->getType() - ->isIntegralOrEnumerationType()) + ->getType() + ->isIntegralOrEnumerationType()) return; CallName = "operator new"; } diff --git a/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.cpp index 7a989b07119a..1a23473fdd22 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.cpp @@ -57,6 +57,17 @@ AST_MATCHER(QualType, isPointerType) { } // namespace +MultiLevelImplicitPointerConversionCheck:: + MultiLevelImplicitPointerConversionCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), EnableInC(Options.get("EnableInC", true)) { +} + +void MultiLevelImplicitPointerConversionCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "EnableInC", EnableInC); +} + void MultiLevelImplicitPointerConversionCheck::registerMatchers( MatchFinder *Finder) { Finder->addMatcher( diff --git a/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.h b/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.h index 13228145ff35..ef5f9f103c89 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/MultiLevelImplicitPointerConversionCheck.h @@ -21,11 +21,17 @@ namespace clang::tidy::bugprone { class MultiLevelImplicitPointerConversionCheck : public ClangTidyCheck { public: MultiLevelImplicitPointerConversionCheck(StringRef Name, - ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; std::optional getCheckTraversalKind() const override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return EnableInC ? true : LangOpts.CPlusPlus; + } + +private: + bool const EnableInC; }; } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp index 41191a3cfed2..b68888cb5b92 100644 --- a/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp @@ -49,8 +49,6 @@ 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) { @@ -74,6 +72,8 @@ AST_MATCHER(CXXNewExpr, mayThrow) { return !OperatorNew->getType()->castAs()->isNothrow(); } +} // namespace + void MultipleNewInOneExpressionCheck::registerMatchers(MatchFinder *Finder) { auto BadAllocType = recordType(hasDeclaration(cxxRecordDecl(hasName("::std::bad_alloc")))); @@ -95,16 +95,14 @@ void MultipleNewInOneExpressionCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( callExpr( - hasAnyArgument( - expr(HasNewExpr1).bind("arg1")), + hasAnyArgument(expr(HasNewExpr1).bind("arg1")), hasAnyArgument( expr(HasNewExpr2, unless(equalsBoundNode("arg1"))).bind("arg2")), hasAncestor(BadAllocCatchingTryBlock)), this); Finder->addMatcher( cxxConstructExpr( - hasAnyArgument( - expr(HasNewExpr1).bind("arg1")), + hasAnyArgument(expr(HasNewExpr1).bind("arg1")), hasAnyArgument( expr(HasNewExpr2, unless(equalsBoundNode("arg1"))).bind("arg2")), unless(isListInitialization()), diff --git a/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp index 977241e91b9a..203170d55f69 100644 --- a/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/NotNullTerminatedResultCheck.cpp @@ -677,7 +677,7 @@ void NotNullTerminatedResultCheck::registerMatchers(MatchFinder *Finder) { std::optional SourcePos, unsigned LengthPos, bool WithIncrease) : Name(Name), DestinationPos(DestinationPos), SourcePos(SourcePos), - LengthPos(LengthPos), WithIncrease(WithIncrease){}; + LengthPos(LengthPos), WithIncrease(WithIncrease) {}; StringRef Name; std::optional DestinationPos; @@ -702,17 +702,16 @@ void NotNullTerminatedResultCheck::registerMatchers(MatchFinder *Finder) { return hasArgument( CC.LengthPos, allOf( - anyOf( - ignoringImpCasts(integerLiteral().bind(WrongLengthExprName)), - allOf(unless(hasDefinition(SizeOfCharExpr)), - allOf(CC.WithIncrease - ? ignoringImpCasts(hasDefinition(HasIncOp)) - : ignoringImpCasts(allOf( - unless(hasDefinition(HasIncOp)), - anyOf(hasDefinition(binaryOperator().bind( - UnknownLengthName)), - hasDefinition(anything())))), - AnyOfWrongLengthInit))), + anyOf(ignoringImpCasts(integerLiteral().bind(WrongLengthExprName)), + allOf(unless(hasDefinition(SizeOfCharExpr)), + allOf(CC.WithIncrease + ? ignoringImpCasts(hasDefinition(HasIncOp)) + : ignoringImpCasts( + allOf(unless(hasDefinition(HasIncOp)), + hasDefinition(optionally( + binaryOperator().bind( + UnknownLengthName))))), + AnyOfWrongLengthInit))), expr().bind(LengthExprName))); }; diff --git a/clang-tools-extra/clang-tidy/bugprone/PosixReturnCheck.h b/clang-tools-extra/clang-tidy/bugprone/PosixReturnCheck.h index 254ae6c7f1ac..e9d2263b06bc 100644 --- a/clang-tools-extra/clang-tidy/bugprone/PosixReturnCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/PosixReturnCheck.h @@ -13,7 +13,7 @@ namespace clang::tidy::bugprone { -class PosixReturnCheck: public ClangTidyCheck { +class PosixReturnCheck : public ClangTidyCheck { public: PosixReturnCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} diff --git a/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp index 95fbca2ad008..e717564847e4 100644 --- a/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/RedundantBranchConditionCheck.cpp @@ -1,4 +1,4 @@ -//===--- RedundantBranchConditionCheck.cpp - clang-tidy -------------------------===// +//===--- RedundantBranchConditionCheck.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. @@ -70,7 +70,8 @@ void RedundantBranchConditionCheck::registerMatchers(MatchFinder *Finder) { // FIXME: Handle longer conjunctive and disjunctive clauses. } -void RedundantBranchConditionCheck::check(const MatchFinder::MatchResult &Result) { +void RedundantBranchConditionCheck::check( + const MatchFinder::MatchResult &Result) { const auto *OuterIf = Result.Nodes.getNodeAs(OuterIfStr); const auto *InnerIf = Result.Nodes.getNodeAs(InnerIfStr); const auto *CondVar = Result.Nodes.getNodeAs(CondVarStr); diff --git a/clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.cpp index c066b3e7b19a..d5cbb847eea9 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.cpp @@ -22,7 +22,9 @@ constexpr llvm::StringLiteral MinimalConformingFunctions[] = { // mentioned POSIX specification was not updated after 'quick_exit' appeared // in the C11 standard. // Also, we want to keep the "minimal set" a subset of the "POSIX set". -// The list is repeated in bugprone-signal-handler.rst and should be kept up to date. +// The list is repeated in bugprone-signal-handler.rst and should be kept up to +// date. +// clang-format off constexpr llvm::StringLiteral POSIXConformingFunctions[] = { "_Exit", "_exit", @@ -215,7 +217,9 @@ constexpr llvm::StringLiteral POSIXConformingFunctions[] = { "wmemcpy", "wmemmove", "wmemset", - "write"}; + "write" +}; +// clang-format on using namespace clang::ast_matchers; @@ -322,12 +326,12 @@ SourceRange getSourceRangeOfStmt(const Stmt *S, ASTContext &Ctx) { return P.getSourceRange(); } -} // namespace - AST_MATCHER(FunctionDecl, isStandardFunction) { return isStandardFunction(&Node); } +} // namespace + SignalHandlerCheck::SignalHandlerCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/bugprone/SpuriouslyWakeUpFunctionsCheck.h b/clang-tools-extra/clang-tidy/bugprone/SpuriouslyWakeUpFunctionsCheck.h index 5e143411f9b6..098299aea7de 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SpuriouslyWakeUpFunctionsCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/SpuriouslyWakeUpFunctionsCheck.h @@ -13,9 +13,9 @@ namespace clang::tidy::bugprone { -/// Finds ``cnd_wait``, ``cnd_timedwait``, ``wait``, ``wait_for``, or -/// ``wait_until`` function calls when the function is not invoked from a loop -/// that checks whether a condition predicate holds or the function has a +/// Finds ``cnd_wait``, ``cnd_timedwait``, ``wait``, ``wait_for``, or +/// ``wait_until`` function calls when the function is not invoked from a loop +/// that checks whether a condition predicate holds or the function has a /// condition parameter. /// /// For the user-facing documentation see: diff --git a/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp index 682478ecead0..5d9e91e0b82c 100644 --- a/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp @@ -46,7 +46,8 @@ using ast_matchers::stmtExpr; using ast_matchers::unless; using ast_matchers::voidType; -const Expr *getCondition(const BoundNodes &Nodes, const StringRef NodeId) { +static const Expr *getCondition(const BoundNodes &Nodes, + const StringRef NodeId) { const auto *If = Nodes.getNodeAs(NodeId); if (If != nullptr) return If->getCond(); diff --git a/clang-tools-extra/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp index 72e680d25cb8..444f3081b704 100644 --- a/clang-tools-extra/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp @@ -47,8 +47,9 @@ void StringLiteralWithEmbeddedNulCheck::registerMatchers(MatchFinder *Finder) { // Detect passing a suspicious string literal to a string constructor. // example: std::string str = "abc\0def"; - Finder->addMatcher(traverse(TK_AsIs, - cxxConstructExpr(StringConstructorExpr, hasArgument(0, StrLitWithNul))), + Finder->addMatcher( + traverse(TK_AsIs, cxxConstructExpr(StringConstructorExpr, + hasArgument(0, StrLitWithNul))), this); // Detect passing a suspicious string literal through an overloaded operator. diff --git a/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp index f944ae6c5b9e..20789b3123e2 100644 --- a/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/StringviewNullptrCheck.cpp @@ -33,7 +33,7 @@ AST_MATCHER(clang::VarDecl, isDirectInitialization) { } // namespace -RewriteRuleWith stringviewNullptrCheckImpl() { +static RewriteRuleWith stringviewNullptrCheckImpl() { auto ConstructionWarning = cat("constructing basic_string_view from null is undefined; replace with " "the default constructor"); diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h b/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h index 63c4bea6e565..c8a70c5f0704 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h @@ -1,4 +1,4 @@ -//===--- SuspiciousEnumUsageCheck.h - clang-tidy--------------------------*- C++ -*-===// +//===--- SuspiciousEnumUsageCheck.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. @@ -28,7 +28,7 @@ class SuspiciousEnumUsageCheck : public ClangTidyCheck { } private: - void checkSuspiciousBitmaskUsage(const Expr*, const EnumDecl*); + void checkSuspiciousBitmaskUsage(const Expr *, const EnumDecl *); const bool StrictMode; }; diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp index a488d35ca744..cc1bd622039b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMemsetUsageCheck.cpp @@ -21,8 +21,7 @@ void SuspiciousMemsetUsageCheck::registerMatchers(MatchFinder *Finder) { // Match the standard memset: // void *memset(void *buffer, int fill_char, size_t byte_count); auto MemsetDecl = - functionDecl(hasName("::memset"), - parameterCountIs(3), + functionDecl(hasName("::memset"), parameterCountIs(3), hasParameter(0, hasType(pointerType(pointee(voidType())))), hasParameter(1, hasType(isInteger())), hasParameter(2, hasType(isInteger()))); diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp index 92b772dd26d4..e93ba760f447 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp @@ -18,8 +18,7 @@ namespace clang::tidy::bugprone { void SuspiciousSemicolonCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( stmt(anyOf(ifStmt(hasThen(nullStmt().bind("semi")), - unless(hasElse(stmt())), - unless(isConstexpr())), + unless(hasElse(stmt())), unless(isConstexpr())), forStmt(hasBody(nullStmt().bind("semi"))), cxxForRangeStmt(hasBody(nullStmt().bind("semi"))), whileStmt(hasBody(nullStmt().bind("semi"))))) diff --git a/clang-tools-extra/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp index 9db14ab40ce5..a4c1fd53dfbe 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp @@ -1,4 +1,4 @@ -//===--- UndelegatedConstructorCheck.cpp - clang-tidy --------------------------===// +//===--- UndelegatedConstructorCheck.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. diff --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp index ca4562aa9a44..5e220017c97f 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnhandledExceptionAtNewCheck.cpp @@ -12,6 +12,7 @@ using namespace clang::ast_matchers; namespace clang::tidy::bugprone { +namespace { AST_MATCHER_P(CXXTryStmt, hasHandlerFor, ast_matchers::internal::Matcher, InnerMatcher) { @@ -36,6 +37,8 @@ AST_MATCHER(CXXNewExpr, mayThrow) { return !OperatorNew->getType()->castAs()->isNothrow(); } +} // namespace + UnhandledExceptionAtNewCheck::UnhandledExceptionAtNewCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} diff --git a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp index d96e7524172b..b17d3868dd76 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp @@ -37,8 +37,8 @@ void UnusedRaiiCheck::registerMatchers(MatchFinder *Finder) { } template -void reportDiagnostic(DiagnosticBuilder D, const T *Node, SourceRange SR, - bool DefaultConstruction) { +static void reportDiagnostic(DiagnosticBuilder D, const T *Node, SourceRange SR, + bool DefaultConstruction) { const char *Replacement = " give_me_a_name"; // If this is a default ctor we have to remove the parens or we'll introduce a diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp index 960133159dbb..1bcacf96a412 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp @@ -242,7 +242,7 @@ void UseAfterMoveFinder::getUsesAndReinits( }); } -bool isStandardSmartPointer(const ValueDecl *VD) { +static bool isStandardSmartPointer(const ValueDecl *VD) { const Type *TheType = VD->getType().getNonReferenceType().getTypePtrOrNull(); if (!TheType) return false; diff --git a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp index cc092a9627c5..66fedabaf3ca 100644 --- a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp @@ -250,8 +250,7 @@ class CERTModule : public ClangTidyModule { "cert-dcl51-cpp"); CheckFactories.registerCheck( "cert-dcl54-cpp"); - CheckFactories.registerCheck( - "cert-dcl58-cpp"); + CheckFactories.registerCheck("cert-dcl58-cpp"); CheckFactories.registerCheck( "cert-dcl59-cpp"); // ERR @@ -278,8 +277,7 @@ class CERTModule : public ClangTidyModule { "cert-oop54-cpp"); CheckFactories.registerCheck( "cert-oop57-cpp"); - CheckFactories.registerCheck( - "cert-oop58-cpp"); + CheckFactories.registerCheck("cert-oop58-cpp"); // C checkers // ARR @@ -360,6 +358,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the CERTModule. -volatile int CERTModuleAnchorSource = 0; +volatile int CERTModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp b/clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp index a88ee46fc4ab..6c58c506dc90 100644 --- a/clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp @@ -27,12 +27,14 @@ class ConcurrencyModule : public ClangTidyModule { } // namespace concurrency -// Register the ConcurrencyTidyModule using this statically initialized variable. +// Register the ConcurrencyTidyModule using this statically initialized +// variable. static ClangTidyModuleRegistry::Add X("concurrency-module", "Adds concurrency checks."); // This anchor is used to force the linker to link in the generated object file // and thus register the ConcurrencyModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int ConcurrencyModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt index b023f76a2543..2fb4d7f1d734 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -33,6 +33,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule STATIC RvalueReferenceParamNotMovedCheck.cpp SlicingCheck.cpp SpecialMemberFunctionsCheck.cpp + UseEnumClassCheck.cpp VirtualClassDestructorCheck.cpp LINK_LIBS diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp index 6adef0426434..4b3b7bf963fd 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -48,6 +48,7 @@ #include "RvalueReferenceParamNotMovedCheck.h" #include "SlicingCheck.h" #include "SpecialMemberFunctionsCheck.h" +#include "UseEnumClassCheck.h" #include "VirtualClassDestructorCheck.h" namespace clang::tidy { @@ -131,6 +132,8 @@ class CppCoreGuidelinesModule : public ClangTidyModule { CheckFactories.registerCheck("cppcoreguidelines-slicing"); CheckFactories.registerCheck( "cppcoreguidelines-use-default-member-init"); + CheckFactories.registerCheck( + "cppcoreguidelines-use-enum-class"); CheckFactories.registerCheck( "cppcoreguidelines-c-copy-assignment-signature"); CheckFactories.registerCheck( @@ -156,6 +159,7 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the CppCoreGuidelinesModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int CppCoreGuidelinesModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp index d665c47d12bb..aaaaf6b66b51 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp @@ -433,21 +433,22 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer( // Gather all fields (direct and indirect) that need to be initialized. SmallPtrSet FieldsToInit; bool AnyMemberHasInitPerUnion = false; - forEachFieldWithFilter(ClassDecl, ClassDecl.fields(), - AnyMemberHasInitPerUnion, [&](const FieldDecl *F) { - if (IgnoreArrays && F->getType()->isArrayType()) - return; - if (F->hasInClassInitializer() && F->getParent()->isUnion()) { - AnyMemberHasInitPerUnion = true; - removeFieldInitialized(F, FieldsToInit); - } - if (!F->hasInClassInitializer() && - utils::type_traits::isTriviallyDefaultConstructible(F->getType(), - Context) && - !isEmpty(Context, F->getType()) && !F->isUnnamedBitField() && - !AnyMemberHasInitPerUnion) - FieldsToInit.insert(F); - }); + forEachFieldWithFilter( + ClassDecl, ClassDecl.fields(), AnyMemberHasInitPerUnion, + [&](const FieldDecl *F) { + if (IgnoreArrays && F->getType()->isArrayType()) + return; + if (F->hasInClassInitializer() && F->getParent()->isUnion()) { + AnyMemberHasInitPerUnion = true; + removeFieldInitialized(F, FieldsToInit); + } + if (!F->hasInClassInitializer() && + utils::type_traits::isTriviallyDefaultConstructible(F->getType(), + Context) && + !isEmpty(Context, F->getType()) && !F->isUnnamedBitField() && + !AnyMemberHasInitPerUnion) + FieldsToInit.insert(F); + }); if (FieldsToInit.empty()) return; @@ -500,17 +501,18 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer( AnyMemberHasInitPerUnion = false; forEachFieldWithFilter(ClassDecl, ClassDecl.fields(), AnyMemberHasInitPerUnion, [&](const FieldDecl *F) { - if (!FieldsToInit.count(F)) - return; - // Don't suggest fixes for enums because we don't know a good default. - // Don't suggest fixes for bitfields because in-class initialization is not - // possible until C++20. - if (F->getType()->isEnumeralType() || - (!getLangOpts().CPlusPlus20 && F->isBitField())) - return; - FieldsToFix.insert(F); - AnyMemberHasInitPerUnion = true; - }); + if (!FieldsToInit.count(F)) + return; + // Don't suggest fixes for enums because we don't + // know a good default. Don't suggest fixes for + // bitfields because in-class initialization is not + // possible until C++20. + if (F->getType()->isEnumeralType() || + (!getLangOpts().CPlusPlus20 && F->isBitField())) + return; + FieldsToFix.insert(F); + AnyMemberHasInitPerUnion = true; + }); if (FieldsToFix.empty()) return; diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h index 00f6e93fec20..02bfaf1205f4 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SlicingCheck.h @@ -14,11 +14,7 @@ namespace clang::tidy::cppcoreguidelines { /// Flags slicing (incomplete copying of an object's state) of member variables -/// or vtable. See: -/// - https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es63-dont-slice -/// for the former, and -/// - https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c145-access-polymorphic-objects-through-pointers-and-references -/// for the latter +/// or vtable. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/slicing.html diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp index b1da3b9861c6..0de143dbb1b8 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp @@ -116,7 +116,8 @@ void SpecialMemberFunctionsCheck::check( if (!MatchedDecl) return; - ClassDefId ID(MatchedDecl->getLocation(), std::string(MatchedDecl->getName())); + ClassDefId ID(MatchedDecl->getLocation(), + std::string(MatchedDecl->getName())); auto StoreMember = [this, &ID](SpecialMemberFunctionData Data) { llvm::SmallVectorImpl &Members = diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h index 9ebc03ed2fa1..dee01cb5a9fd 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h @@ -84,13 +84,12 @@ struct DenseMapInfo< clang::tidy::cppcoreguidelines::SpecialMemberFunctionsCheck::ClassDefId; static inline ClassDefId getEmptyKey() { - return {DenseMapInfo::getEmptyKey(), - "EMPTY"}; + return {DenseMapInfo::getEmptyKey(), "EMPTY"}; } static inline ClassDefId getTombstoneKey() { return {DenseMapInfo::getTombstoneKey(), - "TOMBSTONE"}; + "TOMBSTONE"}; } static unsigned getHashValue(ClassDefId Val) { diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/UseEnumClassCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/UseEnumClassCheck.cpp new file mode 100644 index 000000000000..ec7d9237afa3 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/UseEnumClassCheck.cpp @@ -0,0 +1,42 @@ +//===--- UseEnumClassCheck.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 "UseEnumClassCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::cppcoreguidelines { + +UseEnumClassCheck::UseEnumClassCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreUnscopedEnumsInClasses( + Options.get("IgnoreUnscopedEnumsInClasses", false)) {} + +void UseEnumClassCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreUnscopedEnumsInClasses", + IgnoreUnscopedEnumsInClasses); +} + +void UseEnumClassCheck::registerMatchers(MatchFinder *Finder) { + auto EnumDecl = + IgnoreUnscopedEnumsInClasses + ? enumDecl(unless(isScoped()), unless(hasParent(recordDecl()))) + : enumDecl(unless(isScoped())); + Finder->addMatcher(EnumDecl.bind("unscoped_enum"), this); +} + +void UseEnumClassCheck::check(const MatchFinder::MatchResult &Result) { + const auto *UnscopedEnum = Result.Nodes.getNodeAs("unscoped_enum"); + + diag(UnscopedEnum->getLocation(), + "enum %0 is unscoped, use 'enum class' instead") + << UnscopedEnum; +} + +} // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/UseEnumClassCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/UseEnumClassCheck.h new file mode 100644 index 000000000000..dfa4b7e3fda6 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/UseEnumClassCheck.h @@ -0,0 +1,40 @@ +//===--- UseEnumClassCheck.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_USEENUMCLASSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_USEENUMCLASSCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::cppcoreguidelines { + +/// Finds unscoped (non-class) enum declarations and suggests using enum class +/// instead. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/use-enum-class.html +class UseEnumClassCheck : public ClangTidyCheck { +public: + UseEnumClassCheck(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.CPlusPlus11; + } + std::optional getCheckTraversalKind() const override { + return TraversalKind::TK_IgnoreUnlessSpelledInSource; + } + +private: + const bool IgnoreUnscopedEnumsInClasses; +}; + +} // namespace clang::tidy::cppcoreguidelines + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_USEENUMCLASSCHECK_H diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/VirtualClassDestructorCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/VirtualClassDestructorCheck.cpp index aa70b3896f16..e31d04656567 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/VirtualClassDestructorCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/VirtualClassDestructorCheck.cpp @@ -18,6 +18,8 @@ using namespace clang::ast_matchers; namespace clang::tidy::cppcoreguidelines { +namespace { + AST_MATCHER(CXXRecordDecl, hasPublicVirtualOrProtectedNonVirtualDestructor) { // We need to call Node.getDestructor() instead of matching a // CXXDestructorDecl. Otherwise, tests will fail for class templates, since @@ -33,6 +35,8 @@ AST_MATCHER(CXXRecordDecl, hasPublicVirtualOrProtectedNonVirtualDestructor) { !Destructor->isVirtual())); } +} // namespace + void VirtualClassDestructorCheck::registerMatchers(MatchFinder *Finder) { ast_matchers::internal::Matcher InheritsVirtualMethod = hasAnyBase(hasType(cxxRecordDecl(has(cxxMethodDecl(isVirtual()))))); diff --git a/clang-tools-extra/clang-tidy/darwin/AvoidSpinlockCheck.h b/clang-tools-extra/clang-tidy/darwin/AvoidSpinlockCheck.h index 727878b258b2..5b5285710c3b 100644 --- a/clang-tools-extra/clang-tidy/darwin/AvoidSpinlockCheck.h +++ b/clang-tools-extra/clang-tidy/darwin/AvoidSpinlockCheck.h @@ -19,7 +19,7 @@ namespace clang::tidy::darwin { /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/darwin/avoid-spinlock.html class AvoidSpinlockCheck : public ClangTidyCheck { - public: +public: AvoidSpinlockCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} void registerMatchers(ast_matchers::MatchFinder *Finder) override; @@ -28,4 +28,4 @@ class AvoidSpinlockCheck : public ClangTidyCheck { } // namespace clang::tidy::darwin -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_DARWIN_AVOIDSPINLOCKCHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_DARWIN_AVOIDSPINLOCKCHECK_H diff --git a/clang-tools-extra/clang-tidy/darwin/DarwinTidyModule.cpp b/clang-tools-extra/clang-tidy/darwin/DarwinTidyModule.cpp index 3475deada499..bc8c91a9ed41 100644 --- a/clang-tools-extra/clang-tidy/darwin/DarwinTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/darwin/DarwinTidyModule.cpp @@ -18,8 +18,7 @@ namespace darwin { class DarwinModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { - CheckFactories.registerCheck( - "darwin-avoid-spinlock"); + CheckFactories.registerCheck("darwin-avoid-spinlock"); CheckFactories.registerCheck( "darwin-dispatch-once-nonstatic"); } @@ -33,6 +32,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the DarwinModule. -volatile int DarwinModuleAnchorSource = 0; +volatile int DarwinModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/fuchsia/FuchsiaTidyModule.cpp b/clang-tools-extra/clang-tidy/fuchsia/FuchsiaTidyModule.cpp index 45a79b75e093..d7a70b39bdc5 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/FuchsiaTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/fuchsia/FuchsiaTidyModule.cpp @@ -52,6 +52,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the FuchsiaModule. -volatile int FuchsiaModuleAnchorSource = 0; +volatile int FuchsiaModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp index b5ce23ae8fed..80ff97a76213 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp +++ b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp @@ -49,7 +49,8 @@ bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node, bool MultipleInheritanceCheck::isCurrentClassInterface( const CXXRecordDecl *Node) const { // Interfaces should have no fields. - if (!Node->field_empty()) return false; + if (!Node->field_empty()) + return false; // Interfaces should have exclusively pure methods. return llvm::none_of(Node->methods(), [](const CXXMethodDecl *M) { @@ -68,11 +69,14 @@ bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) { // To be an interface, all base classes must be interfaces as well. for (const auto &I : Node->bases()) { - if (I.isVirtual()) continue; + if (I.isVirtual()) + continue; const auto *Ty = I.getType()->getAs(); - if (!Ty) continue; + if (!Ty) + continue; const RecordDecl *D = Ty->getDecl()->getDefinition(); - if (!D) continue; + if (!D) + continue; const auto *Base = cast(D); if (!isInterface(Base)) { addNodeToInterfaceMap(Node, false); @@ -97,20 +101,25 @@ void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) { // concrete classes unsigned NumConcrete = 0; for (const auto &I : D->bases()) { - if (I.isVirtual()) continue; + if (I.isVirtual()) + continue; const auto *Ty = I.getType()->getAs(); - if (!Ty) continue; + if (!Ty) + continue; const auto *Base = cast(Ty->getDecl()->getDefinition()); - if (!isInterface(Base)) NumConcrete++; + if (!isInterface(Base)) + NumConcrete++; } // Check virtual bases to see if there is more than one concrete // non-virtual base. for (const auto &V : D->vbases()) { const auto *Ty = V.getType()->getAs(); - if (!Ty) continue; + if (!Ty) + continue; const auto *Base = cast(Ty->getDecl()->getDefinition()); - if (!isInterface(Base)) NumConcrete++; + if (!isInterface(Base)) + NumConcrete++; } if (NumConcrete > 1) { diff --git a/clang-tools-extra/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.h b/clang-tools-extra/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.h index fc18273ad73b..6c65c8cfeb22 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.h @@ -14,7 +14,7 @@ namespace clang::tidy::fuchsia { /// Constructing global, non-trivial objects with static storage is -/// disallowed, unless the object is statically initialized with a constexpr +/// disallowed, unless the object is statically initialized with a constexpr /// constructor or has no explicit constructor. /// /// For the user-facing documentation see: diff --git a/clang-tools-extra/clang-tidy/fuchsia/TrailingReturnCheck.h b/clang-tools-extra/clang-tidy/fuchsia/TrailingReturnCheck.h index 2ba8e27d00bf..70551844898f 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/TrailingReturnCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/TrailingReturnCheck.h @@ -13,8 +13,8 @@ namespace clang::tidy::fuchsia { -/// Functions that have trailing returns are disallowed, except for those -/// using decltype specifiers and lambda with otherwise unutterable +/// Functions that have trailing returns are disallowed, except for those +/// using decltype specifiers and lambda with otherwise unutterable /// return types. /// /// For the user-facing documentation see: diff --git a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp index 9c367423fdb5..20bd036fb265 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp +++ b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp @@ -16,10 +16,13 @@ namespace clang::tidy::fuchsia { namespace { AST_MATCHER(CXXRecordDecl, hasDirectVirtualBaseClass) { - if (!Node.hasDefinition()) return false; - if (!Node.getNumVBases()) return false; + if (!Node.hasDefinition()) + return false; + if (!Node.getNumVBases()) + return false; for (const CXXBaseSpecifier &Base : Node.bases()) - if (Base.isVirtual()) return true; + if (Base.isVirtual()) + return true; return false; } } // namespace diff --git a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h index d109bd52a1a6..426d89d046a6 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.h @@ -18,7 +18,7 @@ namespace clang::tidy::fuchsia { /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia/virtual-inheritance.html class VirtualInheritanceCheck : public ClangTidyCheck { - public: +public: VirtualInheritanceCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} void registerMatchers(ast_matchers::MatchFinder *Finder) override; @@ -27,4 +27,4 @@ class VirtualInheritanceCheck : public ClangTidyCheck { } // namespace clang::tidy::fuchsia -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_VIRTUAL_INHERITANCE_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_VIRTUAL_INHERITANCE_H diff --git a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp index 3109bbb3724c..e076b39b5d97 100644 --- a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp @@ -120,8 +120,8 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten); const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() && - DestTypeAsWritten->isRecordType() && - !DestTypeAsWritten->isElaboratedTypeSpecifier(); + DestTypeAsWritten->isRecordType() && + !DestTypeAsWritten->isElaboratedTypeSpecifier(); if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) { // Function pointer/reference casts may be needed to resolve ambiguities in diff --git a/clang-tools-extra/clang-tidy/google/AvoidThrowingObjCExceptionCheck.h b/clang-tools-extra/clang-tidy/google/AvoidThrowingObjCExceptionCheck.h index f8b191a37620..58b46e0a075a 100644 --- a/clang-tools-extra/clang-tidy/google/AvoidThrowingObjCExceptionCheck.h +++ b/clang-tools-extra/clang-tidy/google/AvoidThrowingObjCExceptionCheck.h @@ -20,7 +20,7 @@ namespace clang::tidy::google::objc { /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/google/objc-avoid-throwing-exception.html class AvoidThrowingObjCExceptionCheck : public ClangTidyCheck { - public: +public: AvoidThrowingObjCExceptionCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { @@ -32,4 +32,4 @@ class AvoidThrowingObjCExceptionCheck : public ClangTidyCheck { } // namespace clang::tidy::google::objc -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_AVOID_THROWING_EXCEPTION_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_AVOID_THROWING_EXCEPTION_H diff --git a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp index 6f26de988135..3deea0620514 100644 --- a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp @@ -85,7 +85,7 @@ void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { "%0 explicit expression evaluates to 'false'"; if (const auto *Conversion = - Result.Nodes.getNodeAs("conversion")) { + Result.Nodes.getNodeAs("conversion")) { if (Conversion->isOutOfLine()) return; SourceLocation Loc = Conversion->getLocation(); diff --git a/clang-tools-extra/clang-tidy/google/GlobalNamesInHeadersCheck.cpp b/clang-tools-extra/clang-tidy/google/GlobalNamesInHeadersCheck.cpp index cba8d71ab6a3..459dee124752 100644 --- a/clang-tools-extra/clang-tidy/google/GlobalNamesInHeadersCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/GlobalNamesInHeadersCheck.cpp @@ -1,4 +1,4 @@ -//===--- GlobalNamesInHeadersCheck.cpp - clang-tidy -----------------*- C++ -*-===// +//===--- GlobalNamesInHeadersCheck.cpp - 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. diff --git a/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.cpp b/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.cpp index 018d49fa5a22..9082c9368d87 100644 --- a/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.cpp @@ -52,7 +52,7 @@ FixItHint generateFixItHint(const VarDecl *Decl, bool IsConst) { CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), llvm::StringRef(NewName)); } -} // namespace +} // namespace void GlobalVariableDeclarationCheck::registerMatchers(MatchFinder *Finder) { // need to add two matchers since we need to bind different ids to distinguish diff --git a/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.h b/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.h index 550224b26d14..19e6c5dbc8e2 100644 --- a/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.h +++ b/clang-tools-extra/clang-tidy/google/GlobalVariableDeclarationCheck.h @@ -20,7 +20,7 @@ namespace clang::tidy::google::objc { /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/google/objc-global-variable-declaration.html class GlobalVariableDeclarationCheck : public ClangTidyCheck { - public: +public: GlobalVariableDeclarationCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { @@ -32,4 +32,4 @@ class GlobalVariableDeclarationCheck : public ClangTidyCheck { } // namespace clang::tidy::google::objc -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_GLOBAL_VARIABLE_DECLARATION_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_GLOBAL_VARIABLE_DECLARATION_H diff --git a/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp b/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp index 830a37af1acc..5343e2b3a597 100644 --- a/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp @@ -35,7 +35,7 @@ namespace clang::tidy { namespace google { class GoogleModule : public ClangTidyModule { - public: +public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { CheckFactories.registerCheck( "google-build-explicit-make-pair"); @@ -96,10 +96,10 @@ class GoogleModule : public ClangTidyModule { static ClangTidyModuleRegistry::Add X("google-module", "Adds Google lint checks."); -} // namespace google +} // namespace google // This anchor is used to force the linker to link in the generated object file // and thus register the GoogleModule. -volatile int GoogleModuleAnchorSource = 0; +volatile int GoogleModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/google/IntegerTypesCheck.cpp b/clang-tools-extra/clang-tidy/google/IntegerTypesCheck.cpp index 359d8efd100b..711444fa9bcd 100644 --- a/clang-tools-extra/clang-tidy/google/IntegerTypesCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/IntegerTypesCheck.cpp @@ -172,8 +172,8 @@ void IntegerTypesCheck::check(const MatchFinder::MatchResult &Result) { // We don't add a fix-it as changing the type can easily break code, // e.g. when a function requires a 'long' argument on all platforms. // QualTypes are printed with implicit quotes. - diag(Loc, "consider replacing %0 with '%1'") << BuiltinLoc.getType() - << Replacement; + diag(Loc, "consider replacing %0 with '%1'") + << BuiltinLoc.getType() << Replacement; } } // namespace tidy::google::runtime diff --git a/clang-tools-extra/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp b/clang-tools-extra/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp index 3db27868fcca..c1e421308d77 100644 --- a/clang-tools-extra/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp @@ -21,8 +21,8 @@ UnnamedNamespaceInHeaderCheck::UnnamedNamespaceInHeaderCheck( void UnnamedNamespaceInHeaderCheck::registerMatchers( ast_matchers::MatchFinder *Finder) { - Finder->addMatcher(namespaceDecl(isAnonymous()).bind("anonymousNamespace"), - this); + Finder->addMatcher(namespaceDecl(isAnonymous()).bind("anonymousNamespace"), + this); } void UnnamedNamespaceInHeaderCheck::check( diff --git a/clang-tools-extra/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp b/clang-tools-extra/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp index e9734e54a13e..26d27c19f489 100644 --- a/clang-tools-extra/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp @@ -16,7 +16,7 @@ namespace clang::tidy::google::build { void UsingNamespaceDirectiveCheck::registerMatchers( ast_matchers::MatchFinder *Finder) { - Finder->addMatcher(usingDirectiveDecl().bind("usingNamespace"), this); + Finder->addMatcher(usingDirectiveDecl().bind("usingNamespace"), this); } void UsingNamespaceDirectiveCheck::check( diff --git a/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp b/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp index 2b2acfdf5b08..ed39568ea554 100644 --- a/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp +++ b/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp @@ -24,14 +24,12 @@ void ExceptionBaseclassCheck::registerMatchers(MatchFinder *Finder) { isSameOrDerivedFrom(hasName("::std::exception")))))))))), // This condition is always true, but will bind to the // template value if the thrown type is templated. - anyOf(has(expr( - hasType(substTemplateTypeParmType().bind("templ_type")))), - anything()), + optionally(has( + expr(hasType(substTemplateTypeParmType().bind("templ_type"))))), // Bind to the declaration of the type of the value that - // is thrown. 'anything()' is necessary to always succeed - // in the 'eachOf' because builtin types are not - // 'namedDecl'. - eachOf(has(expr(hasType(namedDecl().bind("decl")))), anything())) + // is thrown. 'optionally' is necessary because builtin types + // are not 'namedDecl'. + optionally(has(expr(hasType(namedDecl().bind("decl")))))) .bind("bad_throw"), this); } diff --git a/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.h b/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.h index 036db35a2290..79d8cf925d1b 100644 --- a/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.h +++ b/clang-tools-extra/clang-tidy/hicpp/ExceptionBaseclassCheck.h @@ -13,7 +13,8 @@ namespace clang::tidy::hicpp { -/// Check for thrown exceptions and enforce they are all derived from std::exception. +/// Check for thrown exceptions and enforce they are all derived from +/// std::exception. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/hicpp/exception-baseclass.html diff --git a/clang-tools-extra/clang-tidy/hicpp/HICPPTidyModule.cpp b/clang-tools-extra/clang-tidy/hicpp/HICPPTidyModule.cpp index 38f3ab5ae85d..65a56be3e5a0 100644 --- a/clang-tools-extra/clang-tidy/hicpp/HICPPTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/hicpp/HICPPTidyModule.cpp @@ -119,6 +119,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the HICPPModule. -volatile int HICPPModuleAnchorSource = 0; +volatile int HICPPModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp b/clang-tools-extra/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp index 7028c3958f10..3f5cd4b47390 100644 --- a/clang-tools-extra/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp +++ b/clang-tools-extra/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp @@ -113,7 +113,7 @@ void MultiwayPathsCoveredCheck::check(const MatchFinder::MatchResult &Result) { } // Warns for degenerated 'switch' statements that neither define a case nor // a default label. - // FIXME: Evaluate, if emitting a fix-it to simplify that statement is + // FIXME: Evaluate, if emitting a fix-it to simplify that statement is // reasonable. if (!SwitchHasDefault && SwitchCaseCount == 0) { diag(Switch->getBeginLoc(), diff --git a/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp b/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp index e6d64ff61d26..b8b75b7ccaef 100644 --- a/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp @@ -30,6 +30,7 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the LinuxKernelModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int LinuxKernelModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/linuxkernel/MustCheckErrsCheck.cpp b/clang-tools-extra/clang-tidy/linuxkernel/MustCheckErrsCheck.cpp index 149d0a94eaa5..ce501ac2acca 100644 --- a/clang-tools-extra/clang-tidy/linuxkernel/MustCheckErrsCheck.cpp +++ b/clang-tools-extra/clang-tidy/linuxkernel/MustCheckErrsCheck.cpp @@ -19,8 +19,7 @@ void MustCheckErrsCheck::registerMatchers(MatchFinder *Finder) { "ERR_CAST", "PTR_ERR_OR_ZERO")); auto NonCheckingStmts = stmt(anyOf(compoundStmt(), labelStmt())); Finder->addMatcher( - callExpr(callee(ErrFn), hasParent(NonCheckingStmts)).bind("call"), - this); + callExpr(callee(ErrFn), hasParent(NonCheckingStmts)).bind("call"), this); auto ReturnToCheck = returnStmt(hasReturnValue(callExpr(callee(ErrFn)))); auto ReturnsErrFn = functionDecl(hasDescendant(ReturnToCheck)); diff --git a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp index e749163699b3..ceebde1595e7 100644 --- a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp @@ -57,6 +57,6 @@ static ClangTidyModuleRegistry::Add X("llvm-module", // This anchor is used to force the linker to link in the generated object file // and thus register the LLVMModule. -volatile int LLVMModuleAnchorSource = 0; +volatile int LLVMModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp index 933333777770..4c138bcc564d 100644 --- a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp @@ -14,12 +14,11 @@ using namespace clang::ast_matchers; -namespace clang { -namespace ast_matchers { -AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); } -} // namespace ast_matchers +namespace clang::tidy::llvm_check { -namespace tidy::llvm_check { +namespace { +AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); } +} // namespace void PreferIsaOrDynCastInConditionalsCheck::registerMatchers( MatchFinder *Finder) { @@ -125,5 +124,4 @@ void PreferIsaOrDynCastInConditionalsCheck::check( } } -} // namespace tidy::llvm_check -} // namespace clang +} // namespace clang::tidy::llvm_check diff --git a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h index dd9c5fec6af5..5b611096c25f 100644 --- a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h +++ b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.h @@ -15,8 +15,9 @@ namespace clang::tidy::llvm_check { /// Looks at conditionals and finds and replaces cases of ``cast<>``, which will /// assert rather than return a null pointer, and ``dyn_cast<>`` where -/// the return value is not captured. Additionally, finds and replaces cases that match the -/// pattern ``var && isa(var)``, where ``var`` is evaluated twice. +/// the return value is not captured. Additionally, finds and replaces cases +/// that match the pattern ``var && isa(var)``, where ``var`` is evaluated +/// twice. /// /// Finds cases like these: /// \code diff --git a/clang-tools-extra/clang-tidy/llvmlibc/CalleeNamespaceCheck.cpp b/clang-tools-extra/clang-tidy/llvmlibc/CalleeNamespaceCheck.cpp index d14cd2721d9a..4bc4d5a4691f 100644 --- a/clang-tools-extra/clang-tidy/llvmlibc/CalleeNamespaceCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvmlibc/CalleeNamespaceCheck.cpp @@ -19,7 +19,7 @@ namespace clang::tidy::llvm_libc { // Gets the outermost namespace of a DeclContext, right under the Translation // Unit. -const DeclContext *getOutermostNamespace(const DeclContext *Decl) { +static const DeclContext *getOutermostNamespace(const DeclContext *Decl) { const DeclContext *Parent = Decl->getParent(); if (Parent->isTranslationUnit()) return Decl; diff --git a/clang-tools-extra/clang-tidy/llvmlibc/LLVMLibcTidyModule.cpp b/clang-tools-extra/clang-tidy/llvmlibc/LLVMLibcTidyModule.cpp index 7f26840be737..562d71a0891c 100644 --- a/clang-tools-extra/clang-tidy/llvmlibc/LLVMLibcTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/llvmlibc/LLVMLibcTidyModule.cpp @@ -39,6 +39,7 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the LLVMLibcModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int LLVMLibcModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp index 54bcebca7e18..6ddebcbc0e15 100644 --- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp @@ -92,6 +92,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the MiscModule. -volatile int MiscModuleAnchorSource = 0; +volatile int MiscModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp b/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp index faff1c17fc61..37fbd8c0d725 100644 --- a/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp @@ -38,8 +38,7 @@ void StaticAssertCheck::registerMatchers(MatchFinder *Finder) { binaryOperator( hasAnyOperatorName("&&", "=="), hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))), - anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)), - anything())) + optionally(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)))) .bind("assertExprRoot"), IsAlwaysFalse); auto NonConstexprFunctionCall = @@ -52,12 +51,10 @@ void StaticAssertCheck::registerMatchers(MatchFinder *Finder) { auto NonConstexprCode = expr(anyOf(NonConstexprFunctionCall, NonConstexprVariableReference)); auto AssertCondition = - expr( - anyOf(expr(ignoringParenCasts(anyOf( - AssertExprRoot, unaryOperator(hasUnaryOperand( - ignoringParenCasts(AssertExprRoot)))))), - anything()), - unless(NonConstexprCode), unless(hasDescendant(NonConstexprCode))) + expr(optionally(expr(ignoringParenCasts(anyOf( + AssertExprRoot, unaryOperator(hasUnaryOperand( + ignoringParenCasts(AssertExprRoot))))))), + unless(NonConstexprCode), unless(hasDescendant(NonConstexprCode))) .bind("condition"); auto Condition = anyOf(ignoringParenImpCasts(callExpr( diff --git a/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h index 0d4df97c7bdf..e3cc4c5e6cd4 100644 --- a/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h +++ b/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h @@ -13,7 +13,7 @@ namespace clang::tidy::misc { -///checks for locations that do not throw by value +/// Checks for locations that do not throw by value // or catch by reference. // The check is C++ only. It checks that all throw locations // throw by value and not by pointer. Additionally it diff --git a/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp b/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp index a41524af5680..c9477327742d 100644 --- a/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/AvoidBindCheck.cpp @@ -452,8 +452,8 @@ static bool isFixitSupported(const CallableInfo &Callee, return true; } -const FunctionDecl *getCallOperator(const CXXRecordDecl *Callable, - size_t NumArgs) { +static const FunctionDecl *getCallOperator(const CXXRecordDecl *Callable, + size_t NumArgs) { std::vector Candidates = findCandidateCallOperators(Callable, NumArgs); if (Candidates.size() != 1) @@ -462,7 +462,7 @@ const FunctionDecl *getCallOperator(const CXXRecordDecl *Callable, return Candidates.front(); } -const FunctionDecl * +static const FunctionDecl * getCallMethodDecl(const MatchFinder::MatchResult &Result, CallableType Type, CallableMaterializationKind Materialization) { diff --git a/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.h b/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.h index eb791ec12814..09cfebef48d4 100644 --- a/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/DeprecatedIosBaseAliasesCheck.h @@ -13,8 +13,8 @@ namespace clang::tidy::modernize { -/// This check warns the uses of the deprecated member types of ``std::ios_base`` -/// and replaces those that have a non-deprecated equivalent. +/// This check warns the uses of the deprecated member types of +/// ``std::ios_base`` and replaces those that have a non-deprecated equivalent. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/modernize/deprecated-ios-base-aliases.html diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp index eb11ae616202..6c6c626ec4fe 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -114,7 +114,7 @@ arrayConditionMatcher(internal::Matcher LimitExpr) { /// Client code will need to make sure that: /// - The index variable is only used as an array index. /// - All arrays indexed by the loop are the same. -StatementMatcher makeArrayLoopMatcher() { +static StatementMatcher makeArrayLoopMatcher() { StatementMatcher ArrayBoundMatcher = expr(hasType(isInteger())).bind(ConditionBoundName); @@ -155,7 +155,7 @@ StatementMatcher makeArrayLoopMatcher() { /// /// Client code will need to make sure that: /// - The two containers on which 'begin' and 'end' are called are the same. -StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { +static StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { auto BeginNameMatcher = IsReverse ? hasAnyName("rbegin", "crbegin") : hasAnyName("begin", "cbegin"); @@ -267,7 +267,7 @@ StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { /// - The index variable is only used in overloaded operator[] or /// container.at(). /// - The container's iterators would not be invalidated during the loop. -StatementMatcher makePseudoArrayLoopMatcher() { +static StatementMatcher makePseudoArrayLoopMatcher() { // Test that the incoming type has a record declaration that has methods // called 'begin' and 'end'. If the incoming type is const, then make sure // these methods are also marked const. diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp index 93c231b0bdf0..3d0a1f01725f 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp @@ -688,9 +688,8 @@ bool ForLoopIndexUseVisitor::TraverseArraySubscriptExpr(ArraySubscriptExpr *E) { if (!isIndexInSubscriptExpr(E->getIdx(), IndexVar)) return VisitorBase::TraverseArraySubscriptExpr(E); - if ((ContainerExpr && - !areSameExpr(Context, Arr->IgnoreParenImpCasts(), - ContainerExpr->IgnoreParenImpCasts())) || + if ((ContainerExpr && !areSameExpr(Context, Arr->IgnoreParenImpCasts(), + ContainerExpr->IgnoreParenImpCasts())) || !arrayMatchesBoundExpr(Context, Arr->IgnoreImpCasts()->getType(), ArrayBoundExpr)) { // If we have already discovered the array being indexed and this isn't it diff --git a/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp index ae88ec2abaf7..c2db858f72e3 100644 --- a/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp @@ -161,7 +161,7 @@ class MacroToEnumCallbacks : public PPCallbacks { checkName(MacroNameTok); } void Elifdef(SourceLocation Loc, SourceRange ConditionRange, - SourceLocation IfLoc) override { + SourceLocation IfLoc) override { PPCallbacks::Elifdef(Loc, ConditionRange, IfLoc); } void Elifndef(SourceLocation Loc, const Token &MacroNameTok, @@ -169,7 +169,7 @@ class MacroToEnumCallbacks : public PPCallbacks { checkName(MacroNameTok); } void Elifndef(SourceLocation Loc, SourceRange ConditionRange, - SourceLocation IfLoc) override { + SourceLocation IfLoc) override { PPCallbacks::Elifndef(Loc, ConditionRange, IfLoc); } void Endif(SourceLocation Loc, SourceLocation IfLoc) override; @@ -316,8 +316,7 @@ void MacroToEnumCallbacks::FileChanged(SourceLocation Loc, CurrentFile = &Files.back(); } -bool MacroToEnumCallbacks::isInitializer(ArrayRef MacroTokens) -{ +bool MacroToEnumCallbacks::isInitializer(ArrayRef MacroTokens) { IntegralLiteralExpressionMatcher Matcher(MacroTokens, LangOpts.C99 == 0); bool Matched = Matcher.match(); bool IsC = !LangOpts.CPlusPlus; @@ -328,7 +327,6 @@ bool MacroToEnumCallbacks::isInitializer(ArrayRef MacroTokens) return Matched; } - // Any defined but rejected macro is scanned for identifiers that // are to be excluded as enums. void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok, @@ -444,8 +442,8 @@ void MacroToEnumCallbacks::invalidateExpressionNames() { } void MacroToEnumCallbacks::EndOfMainFile() { - invalidateExpressionNames(); - issueDiagnostics(); + invalidateExpressionNames(); + issueDiagnostics(); } void MacroToEnumCallbacks::invalidateRange(SourceRange Range) { @@ -517,7 +515,8 @@ void MacroToEnumCallbacks::fixEnumMacro(const MacroList &MacroList) const { void MacroToEnumCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { - auto Callback = std::make_unique(this, getLangOpts(), SM); + auto Callback = + std::make_unique(this, getLangOpts(), SM); PPCallback = Callback.get(); PP->addPPCallbacks(std::move(Callback)); } @@ -540,7 +539,7 @@ void MacroToEnumCheck::check( const ast_matchers::MatchFinder::MatchResult &Result) { auto *TLDecl = Result.Nodes.getNodeAs("top"); if (TLDecl == nullptr) - return; + return; SourceRange Range = TLDecl->getSourceRange(); if (auto *TemplateFn = Result.Nodes.getNodeAs("top")) { diff --git a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp index d1d7e9dcfa9c..deef3586628c 100644 --- a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -361,8 +361,7 @@ bool MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag, Diag << FixItHint::CreateRemoval( SourceRange(NewStart, InitRange.getBegin())); Diag << FixItHint::CreateRemoval(SourceRange(InitRange.getEnd(), NewEnd)); - } - else { + } else { // New array expression with default/value initialization: // smart_ptr(new int[5]()); // smart_ptr(new Foo[5]()); diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index fc46c72982fd..0cf59b6e0216 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -110,11 +110,11 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck( "modernize-use-default-member-init"); CheckFactories.registerCheck("modernize-use-emplace"); - CheckFactories.registerCheck("modernize-use-equals-default"); + CheckFactories.registerCheck( + "modernize-use-equals-default"); CheckFactories.registerCheck( "modernize-use-equals-delete"); - CheckFactories.registerCheck( - "modernize-use-nodiscard"); + CheckFactories.registerCheck("modernize-use-nodiscard"); CheckFactories.registerCheck("modernize-use-noexcept"); CheckFactories.registerCheck("modernize-use-nullptr"); CheckFactories.registerCheck("modernize-use-override"); @@ -136,6 +136,7 @@ static ClangTidyModuleRegistry::Add X("modernize-module", // This anchor is used to force the linker to link in the generated object file // and thus register the ModernizeModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int ModernizeModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/modernize/RedundantVoidArgCheck.cpp b/clang-tools-extra/clang-tidy/modernize/RedundantVoidArgCheck.cpp index 53447c2b960f..5eebccc366fd 100644 --- a/clang-tools-extra/clang-tidy/modernize/RedundantVoidArgCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/RedundantVoidArgCheck.cpp @@ -112,7 +112,8 @@ void RedundantVoidArgCheck::processFunctionDecl( "function declaration"); } -bool isMacroIdentifier(const IdentifierTable &Idents, const Token &ProtoToken) { +static bool isMacroIdentifier(const IdentifierTable &Idents, + const Token &ProtoToken) { if (!ProtoToken.is(tok::TokenKind::raw_identifier)) return false; diff --git a/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp index 4587b0867078..1ad31d315dc2 100644 --- a/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -141,8 +141,7 @@ void ReplaceAutoPtrCheck::check(const MatchFinder::MatchResult &Result) { "auto_ptr") return; - SourceLocation EndLoc = - AutoPtrLoc.getLocWithOffset(strlen("auto_ptr") - 1); + SourceLocation EndLoc = AutoPtrLoc.getLocWithOffset(strlen("auto_ptr") - 1); diag(AutoPtrLoc, "auto_ptr is deprecated, use unique_ptr instead") << FixItHint::CreateReplacement(SourceRange(AutoPtrLoc, EndLoc), "unique_ptr"); diff --git a/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp index 7a2d804e173c..f4b63087b723 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp @@ -38,12 +38,11 @@ size_t getTypeNameLength(bool RemoveStars, StringRef Text) { else if (C == '>') --TemplateTypenameCntr; const CharType NextChar = - isAlphanumeric(C) - ? Alpha - : (isWhitespace(C) || - (!RemoveStars && TemplateTypenameCntr == 0 && C == '*')) - ? Space - : Punctuation; + isAlphanumeric(C) ? Alpha + : (isWhitespace(C) || + (!RemoveStars && TemplateTypenameCntr == 0 && C == '*')) + ? Space + : Punctuation; if (NextChar != Space) { ++NumChars; // Count the non-space character. if (LastChar == Space && NextChar == Alpha && BeforeSpace == Alpha) @@ -444,10 +443,10 @@ void UseAutoCheck::check(const MatchFinder::MatchResult &Result) { replaceIterators(Decl, Result.Context); } else if (const auto *Decl = Result.Nodes.getNodeAs(DeclWithNewId)) { - replaceExpr(Decl, Result.Context, - [](const Expr *Expr) { return Expr->getType(); }, - "use auto when initializing with new to avoid " - "duplicating the type name"); + replaceExpr( + Decl, Result.Context, [](const Expr *Expr) { return Expr->getType(); }, + "use auto when initializing with new to avoid " + "duplicating the type name"); } else if (const auto *Decl = Result.Nodes.getNodeAs(DeclWithCastId)) { replaceExpr( diff --git a/clang-tools-extra/clang-tidy/modernize/UseBoolLiteralsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBoolLiteralsCheck.cpp index c8e6bf47bb82..339462093a6d 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseBoolLiteralsCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseBoolLiteralsCheck.cpp @@ -26,13 +26,12 @@ void UseBoolLiteralsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { void UseBoolLiteralsCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( - traverse( - TK_AsIs, - implicitCastExpr( - has(ignoringParenImpCasts(integerLiteral().bind("literal"))), - hasImplicitDestinationType(qualType(booleanType())), - unless(isInTemplateInstantiation()), - anyOf(hasParent(explicitCastExpr().bind("cast")), anything()))), + traverse(TK_AsIs, + implicitCastExpr( + has(ignoringParenImpCasts(integerLiteral().bind("literal"))), + hasImplicitDestinationType(qualType(booleanType())), + unless(isInTemplateInstantiation()), + optionally(hasParent(explicitCastExpr().bind("cast"))))), this); Finder->addMatcher( diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp index 6040cddf0e52..9e4d184c4b6e 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp @@ -279,7 +279,7 @@ findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) { return Body->getBeginLoc(); } -bool isPrimaryExpression(const Expr *Expression) { +static bool isPrimaryExpression(const Expr *Expression) { // This function is an incomplete approximation of checking whether // an Expr is a primary expression. In particular, if this function // returns true, the expression is a primary expression. The converse diff --git a/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp index 430455a38f39..aaf24eaa33c1 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp @@ -98,8 +98,8 @@ auto hasWantedType(llvm::ArrayRef TypeNames) { // Matches member call expressions of the named method on the listed container // types. -auto cxxMemberCallExprOnContainer( - StringRef MethodName, llvm::ArrayRef ContainerNames) { +auto cxxMemberCallExprOnContainer(StringRef MethodName, + llvm::ArrayRef ContainerNames) { return cxxMemberCallExpr( hasDeclaration(functionDecl(hasName(MethodName))), on(hasTypeOrPointeeType(hasWantedType(ContainerNames)))); diff --git a/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp index 93151024064b..b361ae445653 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp @@ -48,8 +48,8 @@ static std::set getAllDirectBases(const CXXRecordDecl *Record) { /// Returns a matcher that matches member expressions where the base is /// the variable declared as \p Var and the accessed member is the one declared /// as \p Field. -internal::Matcher accessToFieldInVar(const FieldDecl *Field, - const ValueDecl *Var) { +static internal::Matcher accessToFieldInVar(const FieldDecl *Field, + const ValueDecl *Var) { return ignoringImpCasts( memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))), member(fieldDecl(equalsNode(Field))))); diff --git a/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.h b/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.h index 44f26c2bcfc5..04c2177704fb 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.h @@ -1,4 +1,4 @@ -//===--- UseEqualsDefaultCheck.h - clang-tidy--------------------------*- C++ -*-===// +//===--- UseEqualsDefaultCheck.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. diff --git a/clang-tools-extra/clang-tidy/modernize/UseEqualsDeleteCheck.h b/clang-tools-extra/clang-tidy/modernize/UseEqualsDeleteCheck.h index 8545aa2a6396..64f60351c065 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseEqualsDeleteCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseEqualsDeleteCheck.h @@ -1,4 +1,4 @@ -//===--- UseEqualsDeleteCheck.h - clang-tidy---------------------------*- C++ -*-===// +//===--- UseEqualsDeleteCheck.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. diff --git a/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp index a6b00be75abf..c38fb3a01d28 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp @@ -229,7 +229,7 @@ class CastSequenceVisitor : public RecursiveASTVisitor { return true; } - auto* CastSubExpr = C->getSubExpr()->IgnoreParens(); + auto *CastSubExpr = C->getSubExpr()->IgnoreParens(); // Ignore cast expressions which cast nullptr literal. if (isa(CastSubExpr)) { return true; diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp index 2e059f24d47b..2af67f7ccb4c 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp @@ -33,6 +33,8 @@ static bool isNegativeComparison(const Expr *ComparisonExpr) { return false; } +namespace { + struct NotLengthExprForStringNode { NotLengthExprForStringNode(std::string ID, DynTypedNode Node, ASTContext *Context) @@ -91,6 +93,8 @@ AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) { ID, DynTypedNode::create(Node), &(Finder->getASTContext()))); } +} // namespace + UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp index 4e5c6a798be9..ced4825f79a9 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp @@ -17,6 +17,30 @@ #include #include +namespace clang::tidy { + +template <> +struct OptionEnumMapping< + modernize::UseTrailingReturnTypeCheck::TransformLambda> { + static llvm::ArrayRef> + getEnumMapping() { + static constexpr std::pair< + modernize::UseTrailingReturnTypeCheck::TransformLambda, StringRef> + Mapping[] = { + {modernize::UseTrailingReturnTypeCheck::TransformLambda::All, + "all"}, + {modernize::UseTrailingReturnTypeCheck::TransformLambda:: + AllExceptAuto, + "all_except_auto"}, + {modernize::UseTrailingReturnTypeCheck::TransformLambda::None, + "none"}}; + return Mapping; + } +}; + +} // namespace clang::tidy + using namespace clang::ast_matchers; namespace clang::tidy::modernize { @@ -111,10 +135,17 @@ struct UnqualNameVisitor : public RecursiveASTVisitor { private: const FunctionDecl &F; }; + +AST_MATCHER(LambdaExpr, hasExplicitResultType) { + return Node.hasExplicitResultType(); +} + } // namespace constexpr llvm::StringLiteral ErrorMessageOnFunction = "use a trailing return type for this function"; +constexpr llvm::StringLiteral ErrorMessageOnLambda = + "use a trailing return type for this lambda"; static SourceLocation expandIfMacroId(SourceLocation Loc, const SourceManager &SM) { @@ -285,7 +316,6 @@ findReturnTypeAndCVSourceRange(const FunctionDecl &F, const TypeLoc &ReturnLoc, return {}; } - // If the return type has no local qualifiers, it's source range is accurate. if (!hasAnyNestedLocalQualifiers(F.getReturnType())) return ReturnTypeRange; @@ -329,6 +359,48 @@ findReturnTypeAndCVSourceRange(const FunctionDecl &F, const TypeLoc &ReturnLoc, return ReturnTypeRange; } +static SourceLocation findLambdaTrailingReturnInsertLoc( + const CXXMethodDecl *Method, const SourceManager &SM, + const LangOptions &LangOpts, const ASTContext &Ctx) { + // 'requires' keyword is present in lambda declaration + if (Method->getTrailingRequiresClause()) { + SourceLocation ParamEndLoc; + if (Method->param_empty()) + ParamEndLoc = Method->getBeginLoc(); + else + ParamEndLoc = Method->getParametersSourceRange().getEnd(); + + std::pair ParamEndLocInfo = + SM.getDecomposedLoc(ParamEndLoc); + StringRef Buffer = SM.getBufferData(ParamEndLocInfo.first); + + Lexer Lexer(SM.getLocForStartOfFile(ParamEndLocInfo.first), LangOpts, + Buffer.begin(), Buffer.data() + ParamEndLocInfo.second, + Buffer.end()); + + Token Token; + while (!Lexer.LexFromRawLexer(Token)) { + if (Token.is(tok::raw_identifier)) { + IdentifierInfo &Info = Ctx.Idents.get(StringRef( + SM.getCharacterData(Token.getLocation()), Token.getLength())); + Token.setIdentifierInfo(&Info); + Token.setKind(Info.getTokenID()); + } + + if (Token.is(tok::kw_requires)) + return Token.getLocation().getLocWithOffset(-1); + } + + return {}; + } + + // If no requires clause, insert before the body + if (const Stmt *Body = Method->getBody()) + return Body->getBeginLoc().getLocWithOffset(-1); + + return {}; +} + static void keepSpecifiers(std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange, const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx, @@ -382,14 +454,43 @@ static void keepSpecifiers(std::string &ReturnType, std::string &Auto, } } +UseTrailingReturnTypeCheck::UseTrailingReturnTypeCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + TransformFunctions(Options.get("TransformFunctions", true)), + TransformLambdas(Options.get("TransformLambdas", TransformLambda::All)) { + + if (TransformFunctions == false && TransformLambdas == TransformLambda::None) + this->configurationDiag( + "The check 'modernize-use-trailing-return-type' will not perform any " + "analysis because 'TransformFunctions' and 'TransformLambdas' are " + "disabled."); +} + +void UseTrailingReturnTypeCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "TransformFunctions", TransformFunctions); + Options.store(Opts, "TransformLambdas", TransformLambdas); +} + void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) { - auto F = functionDecl( - unless(anyOf(hasTrailingReturn(), returns(voidType()), - cxxConversionDecl(), cxxMethodDecl(isImplicit())))) - .bind("Func"); + auto F = + functionDecl( + unless(anyOf( + hasTrailingReturn(), returns(voidType()), cxxConversionDecl(), + cxxMethodDecl( + anyOf(isImplicit(), + hasParent(cxxRecordDecl(hasParent(lambdaExpr())))))))) + .bind("Func"); + + if (TransformFunctions) { + Finder->addMatcher(F, this); + Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this); + } - Finder->addMatcher(F, this); - Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this); + if (TransformLambdas != TransformLambda::None) + Finder->addMatcher( + lambdaExpr(unless(hasExplicitResultType())).bind("Lambda"), this); } void UseTrailingReturnTypeCheck::registerPPCallbacks( @@ -401,8 +502,13 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) { assert(PP && "Expected registerPPCallbacks() to have been called before so " "preprocessor is available"); - const auto *F = Result.Nodes.getNodeAs("Func"); + if (const auto *Lambda = Result.Nodes.getNodeAs("Lambda")) { + diagOnLambda(Lambda, Result); + return; + } + const auto *Fr = Result.Nodes.getNodeAs("Friend"); + const auto *F = Result.Nodes.getNodeAs("Func"); assert(F && "Matcher is expected to find only FunctionDecls"); // Three-way comparison operator<=> is syntactic sugar and generates implicit @@ -495,4 +601,41 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) { << FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType); } +void UseTrailingReturnTypeCheck::diagOnLambda( + const LambdaExpr *Lambda, + const ast_matchers::MatchFinder::MatchResult &Result) { + + const CXXMethodDecl *Method = Lambda->getCallOperator(); + if (!Method || Lambda->hasExplicitResultType()) + return; + + const ASTContext *Ctx = Result.Context; + const QualType ReturnType = Method->getReturnType(); + + // We can't write 'auto' in C++11 mode, try to write generic msg and bail out. + if (ReturnType->isDependentType() && + Ctx->getLangOpts().LangStd == LangStandard::lang_cxx11) { + if (TransformLambdas == TransformLambda::All) + diag(Lambda->getBeginLoc(), ErrorMessageOnLambda); + return; + } + + if (ReturnType->isUndeducedAutoType() && + TransformLambdas == TransformLambda::AllExceptAuto) + return; + + const SourceLocation TrailingReturnInsertLoc = + findLambdaTrailingReturnInsertLoc(Method, *Result.SourceManager, + getLangOpts(), *Result.Context); + + if (TrailingReturnInsertLoc.isValid()) + diag(Lambda->getBeginLoc(), "use a trailing return type for this lambda") + << FixItHint::CreateInsertion( + TrailingReturnInsertLoc, + " -> " + + ReturnType.getAsString(Result.Context->getPrintingPolicy())); + else + diag(Lambda->getBeginLoc(), ErrorMessageOnLambda); +} + } // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h index 7f20674d15a5..91369919c5d3 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h @@ -26,18 +26,25 @@ struct ClassifiedToken { /// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-trailing-return-type.html class UseTrailingReturnTypeCheck : public ClangTidyCheck { public: - UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + UseTrailingReturnTypeCheck(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; void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + enum TransformLambda { All, AllExceptAuto, None }; + private: Preprocessor *PP = nullptr; + const bool TransformFunctions; + const TransformLambda TransformLambdas; + + void diagOnLambda(const LambdaExpr *Lambda, + const ast_matchers::MatchFinder::MatchResult &Result); }; } // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.h b/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.h index 6f02becc9f9d..80f022159c67 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseTransparentFunctorsCheck.h @@ -26,6 +26,7 @@ class UseTransparentFunctorsCheck : public ClangTidyCheck { void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + private: const bool SafeMode; }; diff --git a/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp index 109d701395cc..1e0a0a551339 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp @@ -39,10 +39,10 @@ void UseUncaughtExceptionsCheck::registerMatchers(MatchFinder *Finder) { this); // CallExpr in initialisation list: warning, fix-it with avoiding narrowing // conversions. - Finder->addMatcher(callExpr(DirectCallToUncaughtException, - hasAncestor(initListExpr())) - .bind("init_call_expr"), - this); + Finder->addMatcher( + callExpr(DirectCallToUncaughtException, hasAncestor(initListExpr())) + .bind("init_call_expr"), + this); } void UseUncaughtExceptionsCheck::check(const MatchFinder::MatchResult &Result) { diff --git a/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.h b/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.h index 79b475c4f850..48677521181f 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseUncaughtExceptionsCheck.h @@ -13,9 +13,10 @@ namespace clang::tidy::modernize { -/// This check will warn on calls to std::uncaught_exception and replace them with calls to -/// std::uncaught_exceptions, since std::uncaught_exception was deprecated in C++17. In case of -/// macro ID there will be only a warning without fixits. +/// This check will warn on calls to std::uncaught_exception and replace them +/// with calls to std::uncaught_exceptions, since std::uncaught_exception was +/// deprecated in C++17. In case of macro ID there will be only a warning +/// without fixits. /// /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-uncaught-exceptions.html diff --git a/clang-tools-extra/clang-tidy/mpi/MPITidyModule.cpp b/clang-tools-extra/clang-tidy/mpi/MPITidyModule.cpp index 166d71e130db..67ae101c18cb 100644 --- a/clang-tools-extra/clang-tidy/mpi/MPITidyModule.cpp +++ b/clang-tools-extra/clang-tidy/mpi/MPITidyModule.cpp @@ -31,6 +31,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the MPIModule. -volatile int MPIModuleAnchorSource = 0; +volatile int MPIModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/objc/AvoidNSErrorInitCheck.h b/clang-tools-extra/clang-tidy/objc/AvoidNSErrorInitCheck.h index 72a6c6512f62..b343cb0c4fa8 100644 --- a/clang-tools-extra/clang-tidy/objc/AvoidNSErrorInitCheck.h +++ b/clang-tools-extra/clang-tidy/objc/AvoidNSErrorInitCheck.h @@ -19,7 +19,7 @@ namespace clang::tidy::objc { /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/objc/avoid-nserror-init.html class AvoidNSErrorInitCheck : public ClangTidyCheck { - public: +public: AvoidNSErrorInitCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { @@ -31,4 +31,4 @@ class AvoidNSErrorInitCheck : public ClangTidyCheck { } // namespace clang::tidy::objc -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_AVOIDNSERRORINITCHECK_H +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_AVOIDNSERRORINITCHECK_H diff --git a/clang-tools-extra/clang-tidy/objc/ForbiddenSubclassingCheck.cpp b/clang-tools-extra/clang-tidy/objc/ForbiddenSubclassingCheck.cpp index 579aa2965071..089538d4c65a 100644 --- a/clang-tools-extra/clang-tidy/objc/ForbiddenSubclassingCheck.cpp +++ b/clang-tools-extra/clang-tidy/objc/ForbiddenSubclassingCheck.cpp @@ -35,14 +35,11 @@ constexpr char DefaultForbiddenSuperClassNames[] = } // namespace -ForbiddenSubclassingCheck::ForbiddenSubclassingCheck( - StringRef Name, - ClangTidyContext *Context) +ForbiddenSubclassingCheck::ForbiddenSubclassingCheck(StringRef Name, + ClangTidyContext *Context) : ClangTidyCheck(Name, Context), - ForbiddenSuperClassNames( - utils::options::parseStringList( - Options.get("ClassNames", DefaultForbiddenSuperClassNames))) { -} + ForbiddenSuperClassNames(utils::options::parseStringList( + Options.get("ClassNames", DefaultForbiddenSuperClassNames))) {} void ForbiddenSubclassingCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( @@ -53,27 +50,22 @@ void ForbiddenSubclassingCheck::registerMatchers(MatchFinder *Finder) { this); } -void ForbiddenSubclassingCheck::check( - const MatchFinder::MatchResult &Result) { - const auto *SubClass = Result.Nodes.getNodeAs( - "subclass"); +void ForbiddenSubclassingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *SubClass = Result.Nodes.getNodeAs("subclass"); assert(SubClass != nullptr); - const auto *SuperClass = Result.Nodes.getNodeAs( - "superclass"); + const auto *SuperClass = + Result.Nodes.getNodeAs("superclass"); assert(SuperClass != nullptr); diag(SubClass->getLocation(), "Objective-C interface %0 subclasses %1, which is not " "intended to be subclassed") - << SubClass - << SuperClass; + << SubClass << SuperClass; } void ForbiddenSubclassingCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { - Options.store( - Opts, - "ForbiddenSuperClassNames", - utils::options::serializeStringList(ForbiddenSuperClassNames)); + Options.store(Opts, "ForbiddenSuperClassNames", + utils::options::serializeStringList(ForbiddenSuperClassNames)); } } // namespace clang::tidy::objc diff --git a/clang-tools-extra/clang-tidy/objc/NSDateFormatterCheck.cpp b/clang-tools-extra/clang-tidy/objc/NSDateFormatterCheck.cpp index 5438c9c892e2..79e9d97d9594 100644 --- a/clang-tools-extra/clang-tidy/objc/NSDateFormatterCheck.cpp +++ b/clang-tools-extra/clang-tidy/objc/NSDateFormatterCheck.cpp @@ -34,7 +34,7 @@ static char ValidDatePatternChars[] = { // A string pattern is valid if all the letters(a-z, A-Z) in it belong to the // set of reserved characters. See: // https://www.unicode.org/reports/tr35/tr35.html#Invalid_Patterns -bool isValidDatePattern(StringRef Pattern) { +static bool isValidDatePattern(StringRef Pattern) { return llvm::all_of(Pattern, [](const auto &PatternChar) { return !isalpha(PatternChar) || llvm::is_contained(ValidDatePatternChars, PatternChar); diff --git a/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp b/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp index b6f8888ed389..56ccf33a6362 100644 --- a/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/objc/ObjCTidyModule.cpp @@ -35,27 +35,24 @@ class ObjCModule : public ClangTidyModule { "objc-dealloc-in-category"); CheckFactories.registerCheck( "objc-forbidden-subclassing"); - CheckFactories.registerCheck( - "objc-missing-hash"); + CheckFactories.registerCheck("objc-missing-hash"); CheckFactories.registerCheck("objc-nsdate-formatter"); CheckFactories.registerCheck( "objc-nsinvocation-argument-lifetime"); CheckFactories.registerCheck( "objc-property-declaration"); - CheckFactories.registerCheck( - "objc-super-self"); + CheckFactories.registerCheck("objc-super-self"); } }; // Register the ObjCTidyModule using this statically initialized variable. -static ClangTidyModuleRegistry::Add X( - "objc-module", - "Adds Objective-C lint checks."); +static ClangTidyModuleRegistry::Add + X("objc-module", "Adds Objective-C lint checks."); } // namespace objc // This anchor is used to force the linker to link in the generated object file // and thus register the ObjCModule. -volatile int ObjCModuleAnchorSource = 0; +volatile int ObjCModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp b/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp index cda24de49186..3b847f51d217 100644 --- a/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp +++ b/clang-tools-extra/clang-tidy/objc/PropertyDeclarationCheck.cpp @@ -88,7 +88,7 @@ bool prefixedPropertyNameValid(llvm::StringRef PropertyName) { auto RegexExp = llvm::Regex(llvm::StringRef(validPropertyNameRegex(false))); return RegexExp.match(PropertyName.substr(Start + 1)); } -} // namespace +} // namespace void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(objcPropertyDecl( diff --git a/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp index 603d0954e1b1..42fb95bf1052 100644 --- a/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/openmp/ExceptionEscapeCheck.cpp @@ -20,8 +20,7 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) { - llvm::SmallVector FunctionsThatShouldNotThrowVec, - IgnoredExceptionsVec; + llvm::SmallVector IgnoredExceptionsVec; llvm::StringSet<> IgnoredExceptions; StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false); diff --git a/clang-tools-extra/clang-tidy/openmp/OpenMPTidyModule.cpp b/clang-tools-extra/clang-tidy/openmp/OpenMPTidyModule.cpp index 37092f49b25d..d9c9d9067340 100644 --- a/clang-tools-extra/clang-tidy/openmp/OpenMPTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/openmp/OpenMPTidyModule.cpp @@ -34,6 +34,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the OpenMPModule. -volatile int OpenMPModuleAnchorSource = 0; +volatile int OpenMPModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.h b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.h index 93ed32e5b878..83af95cd6954 100644 --- a/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.h +++ b/clang-tools-extra/clang-tidy/performance/FasterStringFindCheck.h @@ -25,7 +25,7 @@ namespace clang::tidy::performance { class FasterStringFindCheck : public ClangTidyCheck { public: FasterStringFindCheck(StringRef Name, ClangTidyContext *Context); - bool isLanguageVersionSupported(const LangOptions &LangOpts) const override{ + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus; } void registerMatchers(ast_matchers::MatchFinder *Finder) override; diff --git a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.h b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.h index ab9b89d31f93..8fabbfa2ae7b 100644 --- a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.h +++ b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.h @@ -20,7 +20,7 @@ namespace clang::tidy::performance { class ForRangeCopyCheck : public ClangTidyCheck { public: ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context); - bool isLanguageVersionSupported(const LangOptions &LangOpts) const override{ + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus11; } void storeOptions(ClangTidyOptions::OptionMap &Opts) override; diff --git a/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp b/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp index 86fca0722dcd..1ecf1e14957a 100644 --- a/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp @@ -65,8 +65,7 @@ void ImplicitConversionInLoopCheck::check( const MatchFinder::MatchResult &Result) { const auto *VD = Result.Nodes.getNodeAs("faulty-var"); const auto *Init = Result.Nodes.getNodeAs("init"); - const auto *OperatorCall = - Result.Nodes.getNodeAs("operator-call"); + const auto *OperatorCall = Result.Nodes.getNodeAs("operator-call"); if (const auto *Cleanup = dyn_cast(Init)) Init = Cleanup->getSubExpr(); diff --git a/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.h b/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.h index 2f3c9ae8f2db..d1764070bd4d 100644 --- a/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.h +++ b/clang-tools-extra/clang-tidy/performance/ImplicitConversionInLoopCheck.h @@ -20,9 +20,9 @@ class ImplicitConversionInLoopCheck : public ClangTidyCheck { public: ImplicitConversionInLoopCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} - bool isLanguageVersionSupported(const LangOptions &LangOpts) const override{ - return LangOpts.CPlusPlus11; - } + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; diff --git a/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.cpp b/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.cpp index 94cb7ec38087..9692fcb0c1de 100644 --- a/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.cpp @@ -126,15 +126,14 @@ void InefficientVectorOperationCheck::addMatcher( // // FIXME: Support more types of counter-based loops like decrement loops. Finder->addMatcher( - forStmt( - hasLoopInit(LoopVarInit), - hasCondition(binaryOperator( - hasOperatorName("<"), hasLHS(RefersToLoopVar), - hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar)))) - .bind(LoopEndExprName)))), - hasIncrement(unaryOperator(hasOperatorName("++"), - hasUnaryOperand(RefersToLoopVar))), - HasInterestingLoopBody, InInterestingCompoundStmt) + forStmt(hasLoopInit(LoopVarInit), + hasCondition(binaryOperator( + hasOperatorName("<"), hasLHS(RefersToLoopVar), + hasRHS(expr(unless(hasDescendant(expr(RefersToLoopVar)))) + .bind(LoopEndExprName)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(RefersToLoopVar))), + HasInterestingLoopBody, InInterestingCompoundStmt) .bind(LoopCounterName), this); @@ -179,7 +178,7 @@ void InefficientVectorOperationCheck::registerMatchers(MatchFinder *Finder) { void InefficientVectorOperationCheck::check( const MatchFinder::MatchResult &Result) { - auto* Context = Result.Context; + auto *Context = Result.Context; if (Context->getDiagnostics().hasUncompilableErrorOccurred()) return; diff --git a/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.h b/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.h index 7a2745f7ae5f..4f45ff490633 100644 --- a/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.h +++ b/clang-tools-extra/clang-tidy/performance/InefficientVectorOperationCheck.h @@ -24,7 +24,7 @@ namespace clang::tidy::performance { class InefficientVectorOperationCheck : public ClangTidyCheck { public: InefficientVectorOperationCheck(StringRef Name, ClangTidyContext *Context); - bool isLanguageVersionSupported(const LangOptions &LangOpts) const override{ + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus; } void registerMatchers(ast_matchers::MatchFinder *Finder) override; diff --git a/clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.cpp b/clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.cpp index 75ef7a697031..f458e26d964b 100644 --- a/clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.cpp @@ -78,9 +78,9 @@ void MoveConstArgCheck::registerMatchers(MatchFinder *Finder) { this); } -bool isRValueReferenceParam(const Expr *Invocation, - const QualType *InvocationParmType, - const Expr *Arg) { +static bool isRValueReferenceParam(const Expr *Invocation, + const QualType *InvocationParmType, + const Expr *Arg) { if (Invocation && (*InvocationParmType)->isRValueReferenceType() && Arg->isLValue()) { if (!Invocation->getType()->isRecordType()) diff --git a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp index 9e0fa6f88b36..10ad9ec6fef4 100644 --- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp @@ -81,6 +81,7 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the PerformanceModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int PerformanceModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp index ce360dded863..b6f19811f5e5 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -111,10 +111,11 @@ AST_MATCHER_FUNCTION(StatementMatcher, isConstRefReturningFunctionCall) { // an alias to one of its arguments and the arguments need to be checked // for const use as well. return callExpr(argumentCountIs(0), - callee(functionDecl(returns(hasCanonicalType(matchers::isReferenceToConst())), + callee(functionDecl(returns(hasCanonicalType( + matchers::isReferenceToConst())), unless(cxxMethodDecl(unless(isStatic())))) - .bind(FunctionDeclId))) - .bind(InitFunctionCallId); + .bind(FunctionDeclId))) + .bind(InitFunctionCallId); } AST_MATCHER_FUNCTION_P(StatementMatcher, initializerReturnsReferenceToConst, @@ -234,12 +235,14 @@ UnnecessaryCopyInitialization::UnnecessaryCopyInitialization( Options.get("ExcludedContainerTypes", ""))) {} void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) { - auto LocalVarCopiedFrom = [this](const ast_matchers::internal::Matcher &CopyCtorArg) { - return compoundStmt( - forEachDescendant( - declStmt( - unless(has(decompositionDecl())), - has(varDecl(hasLocalStorage(), + auto LocalVarCopiedFrom = + [this](const ast_matchers::internal::Matcher &CopyCtorArg) { + return compoundStmt( + forEachDescendant( + declStmt( + unless(has(decompositionDecl())), + has(varDecl( + hasLocalStorage(), hasType(qualType( hasCanonicalType(allOf( matchers::isExpensiveToCopy(), @@ -256,10 +259,10 @@ void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) { isCopyConstructor())), hasArgument(0, CopyCtorArg)) .bind("ctorCall")))) - .bind("newVarDecl"))) - .bind("declStmt"))) - .bind("blockStmt"); - }; + .bind("newVarDecl"))) + .bind("declStmt"))) + .bind("blockStmt"); + }; Finder->addMatcher( LocalVarCopiedFrom(anyOf( diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.h b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.h index ab0f1ecf6106..38f756f9b452 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.h +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.h @@ -25,7 +25,7 @@ namespace clang::tidy::performance { class UnnecessaryCopyInitialization : public ClangTidyCheck { public: UnnecessaryCopyInitialization(StringRef Name, ClangTidyContext *Context); - bool isLanguageVersionSupported(const LangOptions &LangOpts) const override{ + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus; } void registerMatchers(ast_matchers::MatchFinder *Finder) override; diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp index a877f9a7ee91..d89c3a69fc84 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -50,7 +50,8 @@ UnnecessaryValueParamCheck::UnnecessaryValueParamCheck( utils::IncludeSorter::IS_LLVM), areDiagsSelfContained()), AllowedTypes( - utils::options::parseStringList(Options.get("AllowedTypes", ""))) {} + utils::options::parseStringList(Options.get("AllowedTypes", ""))), + IgnoreCoroutines(Options.get("IgnoreCoroutines", true)) {} void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) { const auto ExpensiveValueParamDecl = parmVarDecl( @@ -61,12 +62,14 @@ void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) { matchers::matchesAnyListedName(AllowedTypes))))))), decl().bind("param")); Finder->addMatcher( - traverse( - TK_AsIs, - functionDecl(hasBody(stmt()), isDefinition(), unless(isImplicit()), - unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))), - has(typeLoc(forEach(ExpensiveValueParamDecl))), - decl().bind("functionDecl"))), + traverse(TK_AsIs, + functionDecl( + hasBody(IgnoreCoroutines ? stmt(unless(coroutineBodyStmt())) + : stmt()), + isDefinition(), unless(isImplicit()), + unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))), + has(typeLoc(forEach(ExpensiveValueParamDecl))), + decl().bind("functionDecl"))), this); } @@ -123,6 +126,7 @@ void UnnecessaryValueParamCheck::storeOptions( Options.store(Opts, "IncludeStyle", Inserter.getStyle()); Options.store(Opts, "AllowedTypes", utils::options::serializeStringList(AllowedTypes)); + Options.store(Opts, "IgnoreCoroutines", IgnoreCoroutines); } void UnnecessaryValueParamCheck::onEndOfTranslationUnit() { diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.h b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.h index 8bfd814d1635..b52043416e76 100644 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.h +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.h @@ -46,6 +46,7 @@ class UnnecessaryValueParamCheck : public ClangTidyCheck { ExprMutationAnalyzer::Memoized MutationAnalyzerCache; utils::IncludeInserter Inserter; const std::vector AllowedTypes; + bool IgnoreCoroutines; }; } // namespace clang::tidy::performance diff --git a/clang-tools-extra/clang-tidy/plugin/ClangTidyPlugin.cpp b/clang-tools-extra/clang-tidy/plugin/ClangTidyPlugin.cpp index 3caa222b9fa4..651a63b3aa97 100644 --- a/clang-tools-extra/clang-tidy/plugin/ClangTidyPlugin.cpp +++ b/clang-tools-extra/clang-tidy/plugin/ClangTidyPlugin.cpp @@ -78,6 +78,7 @@ class ClangTidyPluginAction : public PluginASTAction { // This anchor is used to force the linker to link in the generated object file // and thus register the clang-tidy plugin. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int ClangTidyPluginAnchorSource = 0; static clang::FrontendPluginRegistry::Add diff --git a/clang-tools-extra/clang-tidy/portability/PortabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/portability/PortabilityTidyModule.cpp index a15cb36dfdaf..98853556588b 100644 --- a/clang-tools-extra/clang-tidy/portability/PortabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/portability/PortabilityTidyModule.cpp @@ -42,6 +42,7 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the PortabilityModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int PortabilityModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.h b/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.h index c254a374ee9d..92fc0af98a25 100644 --- a/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.h +++ b/clang-tools-extra/clang-tidy/portability/SIMDIntrinsicsCheck.h @@ -30,7 +30,7 @@ class SIMDIntrinsicsCheck : public ClangTidyCheck { void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - private: +private: llvm::SmallString<32> Std; const bool Suggest; }; diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt index 4be1a8f83133..2c40a863c5b7 100644 --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -58,6 +58,7 @@ add_clang_library(clangTidyReadabilityModule STATIC UniqueptrDeleteReleaseCheck.cpp UppercaseLiteralSuffixCheck.cpp UseAnyOfAllOfCheck.cpp + UseNumericLimitsCheck.cpp UseStdMinMaxCheck.cpp LINK_LIBS diff --git a/clang-tools-extra/clang-tidy/readability/ConstReturnTypeCheck.h b/clang-tools-extra/clang-tidy/readability/ConstReturnTypeCheck.h index c3d779eafecb..e329b3d8496a 100644 --- a/clang-tools-extra/clang-tidy/readability/ConstReturnTypeCheck.h +++ b/clang-tools-extra/clang-tidy/readability/ConstReturnTypeCheck.h @@ -19,16 +19,16 @@ namespace clang::tidy::readability { /// For the user-facing documentation see: /// http://clang.llvm.org/extra/clang-tidy/checks/readability/const-return-type.html class ConstReturnTypeCheck : public ClangTidyCheck { - public: - ConstReturnTypeCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context), - IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} - void storeOptions(ClangTidyOptions::OptionMap &Opts) override; - void registerMatchers(ast_matchers::MatchFinder *Finder) override; - void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +public: + ConstReturnTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - private: - const bool IgnoreMacros; +private: + const bool IgnoreMacros; }; } // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp index bf7a847dff10..ce736a8d1602 100644 --- a/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -16,29 +16,31 @@ using namespace clang::ast_matchers; -namespace clang { -namespace ast_matchers { +namespace clang::tidy::readability { + +namespace { AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam, AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, CXXConstructExpr), - internal::Matcher, ArgMatcher, - internal::Matcher, ParamMatcher) { - BoundNodesTreeBuilder Result; + ast_matchers::internal::Matcher, ArgMatcher, + ast_matchers::internal::Matcher, + ParamMatcher) { + ast_matchers::internal::BoundNodesTreeBuilder Result; // The first argument of an overloaded member operator is the implicit object // argument of the method which should not be matched against a parameter, so // we skip over it here. - BoundNodesTreeBuilder Matches; + ast_matchers::internal::BoundNodesTreeBuilder Matches; unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl())) .matches(Node, Finder, &Matches) ? 1 : 0; int ParamIndex = 0; for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) { - BoundNodesTreeBuilder ArgMatches(*Builder); + ast_matchers::internal::BoundNodesTreeBuilder ArgMatches(*Builder); if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder, &ArgMatches)) { - BoundNodesTreeBuilder ParamMatches(ArgMatches); + ast_matchers::internal::BoundNodesTreeBuilder ParamMatches(ArgMatches); if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl( hasParameter(ParamIndex, ParamMatcher)))), callExpr(callee(functionDecl( @@ -80,9 +82,10 @@ AST_MATCHER(Expr, usedInBooleanContext) { binaryOperator(hasAnyOperatorName("&&", "||")), unaryOperator(hasOperatorName("!")).bind("NegOnSize")))))) .matches(Node, Finder, Builder); - Builder->removeBindings([ExprName](const BoundNodesMap &Nodes) { - return Nodes.getNode(ExprName).getNodeKind().isNone(); - }); + Builder->removeBindings( + [ExprName](const ast_matchers::internal::BoundNodesMap &Nodes) { + return Nodes.getNode(ExprName).getNodeKind().isNone(); + }); return Result; } @@ -107,8 +110,7 @@ AST_MATCHER_P(UserDefinedLiteral, hasLiteral, return false; } -} // namespace ast_matchers -namespace tidy::readability { +} // namespace using utils::isBinaryOrTernary; @@ -407,5 +409,4 @@ void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { << Container; } -} // namespace tidy::readability -} // namespace clang +} // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp b/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp index 1284df6bd99c..30df40bda57d 100644 --- a/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp +++ b/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp @@ -18,6 +18,8 @@ using namespace clang::ast_matchers; namespace clang::tidy::readability { +namespace { + AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } @@ -74,6 +76,8 @@ AST_MATCHER(CXXMethodDecl, usesThis) { return UsageOfThis.Used; } +} // namespace + void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( cxxMethodDecl( @@ -81,7 +85,8 @@ void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) { unless(anyOf( isExpansionInSystemHeader(), isVirtual(), isStatic(), hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(), - cxxDestructorDecl(), cxxConversionDecl(), isTemplate(), + cxxDestructorDecl(), cxxConversionDecl(), + isExplicitObjectMemberFunction(), isTemplate(), isDependentContext(), ofClass(anyOf( isLambda(), diff --git a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp index 3313bcb39b7f..8e3a2e306dbf 100644 --- a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp @@ -108,6 +108,14 @@ class FunctionASTVisitor : public RecursiveASTVisitor { return true; } + bool TraverseConstructorInitializer(CXXCtorInitializer *Init) { + if (CountMemberInitAsStmt) + ++Info.Statements; + + Base::TraverseConstructorInitializer(Init); + return true; + } + struct FunctionInfo { unsigned Lines = 0; unsigned Statements = 0; @@ -120,6 +128,7 @@ class FunctionASTVisitor : public RecursiveASTVisitor { llvm::BitVector TrackedParent; unsigned StructNesting = 0; unsigned CurrentNestingLevel = 0; + bool CountMemberInitAsStmt; }; } // namespace @@ -135,7 +144,9 @@ FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) NestingThreshold( Options.get("NestingThreshold", DefaultNestingThreshold)), VariableThreshold( - Options.get("VariableThreshold", DefaultVariableThreshold)) {} + Options.get("VariableThreshold", DefaultVariableThreshold)), + CountMemberInitAsStmt( + Options.get("CountMemberInitAsStmt", DefaultCountMemberInitAsStmt)) {} void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "LineThreshold", LineThreshold); @@ -144,6 +155,7 @@ void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "ParameterThreshold", ParameterThreshold); Options.store(Opts, "NestingThreshold", NestingThreshold); Options.store(Opts, "VariableThreshold", VariableThreshold); + Options.store(Opts, "CountMemberInitAsStmt", CountMemberInitAsStmt); } void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) { @@ -160,6 +172,7 @@ void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) { FunctionASTVisitor Visitor; Visitor.Info.NestingThreshold = NestingThreshold.value_or(-1); + Visitor.CountMemberInitAsStmt = CountMemberInitAsStmt; Visitor.TraverseDecl(const_cast(Func)); auto &FI = Visitor.Info; diff --git a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h index 106c69ff0739..f668ab18fea5 100644 --- a/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h +++ b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.h @@ -47,6 +47,7 @@ class FunctionSizeCheck : public ClangTidyCheck { const std::optional ParameterThreshold; const std::optional NestingThreshold; const std::optional VariableThreshold; + const bool CountMemberInitAsStmt; static constexpr std::optional DefaultLineThreshold = std::nullopt; static constexpr std::optional DefaultStatementThreshold = 800U; @@ -58,6 +59,7 @@ class FunctionSizeCheck : public ClangTidyCheck { std::nullopt; static constexpr std::optional DefaultVariableThreshold = std::nullopt; + static constexpr bool DefaultCountMemberInitAsStmt = true; }; } // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp b/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp index f9fd1d903e23..20c73299915a 100644 --- a/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp @@ -348,8 +348,8 @@ void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) { implicitCastExpr().bind("implicitCastFromBool"), unless(hasParent(BitfieldConstruct)), // Check also for nested casts, for example: bool -> int -> float. - anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")), - anything()), + optionally( + hasParent(implicitCastExpr().bind("furtherImplicitCast"))), unless(isInTemplateInstantiation()), unless(IsInCompilerGeneratedFunction))), this); diff --git a/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp index 5cc22a22b1f5..10aa779117bb 100644 --- a/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -131,8 +131,8 @@ findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, InconsistentDeclarationsContainer findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration, - const FunctionDecl *ParameterSourceDeclaration, - SourceManager &SM, bool Strict) { + const FunctionDecl *ParameterSourceDeclaration, + SourceManager &SM, bool Strict) { InconsistentDeclarationsContainer InconsistentDeclarations; SourceLocation ParameterSourceLocation = ParameterSourceDeclaration->getLocation(); diff --git a/clang-tools-extra/clang-tidy/readability/MagicNumbersCheck.h b/clang-tools-extra/clang-tidy/readability/MagicNumbersCheck.h index 79787845de61..70a17889d244 100644 --- a/clang-tools-extra/clang-tidy/readability/MagicNumbersCheck.h +++ b/clang-tools-extra/clang-tidy/readability/MagicNumbersCheck.h @@ -43,11 +43,12 @@ class MagicNumbersCheck : public ClangTidyCheck { bool isBitFieldWidth(const clang::ast_matchers::MatchFinder::MatchResult &, const FloatingLiteral &) const { - return false; + return false; } - bool isBitFieldWidth(const clang::ast_matchers::MatchFinder::MatchResult &Result, - const IntegerLiteral &Literal) const; + bool + isBitFieldWidth(const clang::ast_matchers::MatchFinder::MatchResult &Result, + const IntegerLiteral &Literal) const; bool isUserDefinedLiteral( const clang::ast_matchers::MatchFinder::MatchResult &Result, diff --git a/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp b/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp index d42fcba70e81..85852c2c829a 100644 --- a/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp @@ -17,6 +17,8 @@ using namespace clang::ast_matchers; namespace clang::tidy::readability { +namespace { + AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } @@ -214,6 +216,8 @@ AST_MATCHER(CXXMethodDecl, usesThisAsConst) { return UsageOfThis.Usage == Const; } +} // namespace + void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( traverse( diff --git a/clang-tools-extra/clang-tidy/readability/MathMissingParenthesesCheck.cpp b/clang-tools-extra/clang-tidy/readability/MathMissingParenthesesCheck.cpp index 64ce94e3fc1d..1d5c6cca5a82 100644 --- a/clang-tools-extra/clang-tidy/readability/MathMissingParenthesesCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/MathMissingParenthesesCheck.cpp @@ -16,13 +16,15 @@ using namespace clang::ast_matchers; namespace clang::tidy::readability { void MathMissingParenthesesCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher(binaryOperator(unless(hasParent(binaryOperator())), - unless(isAssignmentOperator()), - unless(isComparisonOperator()), - unless(hasAnyOperatorName("&&", "||")), - hasDescendant(binaryOperator())) - .bind("binOp"), - this); + Finder->addMatcher( + binaryOperator( + unless(hasParent(binaryOperator(unless(isAssignmentOperator()), + unless(isComparisonOperator())))), + unless(isAssignmentOperator()), unless(isComparisonOperator()), + unless(hasAnyOperatorName("&&", "||")), + hasDescendant(binaryOperator())) + .bind("binOp"), + this); } static int getPrecedence(const BinaryOperator *BinOp) { @@ -88,10 +90,6 @@ static void addParantheses(const BinaryOperator *BinOp, void MathMissingParenthesesCheck::check( const MatchFinder::MatchResult &Result) { const auto *BinOp = Result.Nodes.getNodeAs("binOp"); - std::vector< - std::pair>> - Insertions; const SourceManager &SM = *Result.SourceManager; const clang::LangOptions &LO = Result.Context->getLangOpts(); addParantheses(BinOp, nullptr, this, SM, LO); diff --git a/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.cpp b/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.cpp index 64dc941569a9..c04bf361c40c 100644 --- a/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.cpp @@ -27,11 +27,15 @@ NamespaceCommentCheck::NamespaceCommentCheck(StringRef Name, "namespace( +(((inline )|([a-zA-Z0-9_:]))+))?\\.? *(\\*/)?$", llvm::Regex::IgnoreCase), ShortNamespaceLines(Options.get("ShortNamespaceLines", 1U)), - SpacesBeforeComments(Options.get("SpacesBeforeComments", 1U)) {} + SpacesBeforeComments(Options.get("SpacesBeforeComments", 1U)), + AllowOmittingNamespaceComments( + Options.get("AllowOmittingNamespaceComments", false)) {} void NamespaceCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "ShortNamespaceLines", ShortNamespaceLines); Options.store(Opts, "SpacesBeforeComments", SpacesBeforeComments); + Options.store(Opts, "AllowOmittingNamespaceComments", + AllowOmittingNamespaceComments); } void NamespaceCommentCheck::registerMatchers(MatchFinder *Finder) { @@ -106,7 +110,7 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { // Currently for nested namespace (n1::n2::...) the AST matcher will match foo // then bar instead of a single match. So if we got a nested namespace we have // to skip the next ones. - for (const auto &EndOfNameLocation : Ends) { + for (const SourceLocation &EndOfNameLocation : Ends) { if (Sources.isBeforeInTranslationUnit(ND->getLocation(), EndOfNameLocation)) return; } @@ -140,6 +144,7 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { SourceRange OldCommentRange(AfterRBrace, AfterRBrace); std::string Message = "%0 not terminated with a closing comment"; + bool HasComment = false; // Try to find existing namespace closing comment on the same line. if (Tok.is(tok::comment) && NextTokenIsOnSameLine) { @@ -158,6 +163,8 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { return; } + HasComment = true; + // Otherwise we need to fix the comment. NeedLineBreak = Comment.starts_with("/*"); OldCommentRange = @@ -180,8 +187,13 @@ void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { } std::string NamespaceNameForDiag = - ND->isAnonymousNamespace() ? "anonymous namespace" - : ("namespace '" + *NamespaceNameAsWritten + "'"); + ND->isAnonymousNamespace() + ? "anonymous namespace" + : ("namespace '" + *NamespaceNameAsWritten + "'"); + + // If no namespace comment is allowed + if (!HasComment && AllowOmittingNamespaceComments) + return; std::string Fix(SpacesBeforeComments, ' '); Fix.append("// namespace"); diff --git a/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.h b/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.h index 7607d37b1b2f..8edd77213f77 100644 --- a/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.h +++ b/clang-tools-extra/clang-tidy/readability/NamespaceCommentCheck.h @@ -34,6 +34,7 @@ class NamespaceCommentCheck : public ClangTidyCheck { llvm::Regex NamespaceCommentPattern; const unsigned ShortNamespaceLines; const unsigned SpacesBeforeComments; + const bool AllowOmittingNamespaceComments; llvm::SmallVector Ends; }; diff --git a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp index 00999ee8310c..91a08b9d8de6 100644 --- a/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp @@ -50,10 +50,9 @@ std::optional findQualToken(const VarDecl *Decl, Qualifier Qual, if (FileRange.isInvalid()) return std::nullopt; - tok::TokenKind Tok = - Qual == Qualifier::Const - ? tok::kw_const - : Qual == Qualifier::Volatile ? tok::kw_volatile : tok::kw_restrict; + tok::TokenKind Tok = Qual == Qualifier::Const ? tok::kw_const + : Qual == Qualifier::Volatile ? tok::kw_volatile + : tok::kw_restrict; return utils::lexer::getQualifyingToken(Tok, FileRange, *Result.Context, *Result.SourceManager); diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp index 4c0812f0e679..dc47c2fb3193 100644 --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -61,6 +61,7 @@ #include "UniqueptrDeleteReleaseCheck.h" #include "UppercaseLiteralSuffixCheck.h" #include "UseAnyOfAllOfCheck.h" +#include "UseNumericLimitsCheck.h" #include "UseStdMinMaxCheck.h" namespace clang::tidy { @@ -173,6 +174,8 @@ class ReadabilityModule : public ClangTidyModule { "readability-uppercase-literal-suffix"); CheckFactories.registerCheck( "readability-use-anyofallof"); + CheckFactories.registerCheck( + "readability-use-numeric-limits"); CheckFactories.registerCheck( "readability-use-std-min-max"); } @@ -186,6 +189,7 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the ReadabilityModule. +// NOLINTNEXTLINE(misc-use-internal-linkage) volatile int ReadabilityModuleAnchorSource = 0; } // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp index 7850a6f29995..053892dffc7b 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantDeclarationCheck.cpp @@ -15,10 +15,14 @@ using namespace clang::ast_matchers; namespace clang::tidy::readability { +namespace { + AST_MATCHER(FunctionDecl, doesDeclarationForceExternallyVisibleDefinition) { return Node.doesDeclarationForceExternallyVisibleDefinition(); } +} // namespace + RedundantDeclarationCheck::RedundantDeclarationCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), diff --git a/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp index 44053ebd21dc..a70719fd8a04 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp @@ -13,7 +13,8 @@ using namespace clang::ast_matchers; namespace clang::tidy::readability { -void RedundantFunctionPtrDereferenceCheck::registerMatchers(MatchFinder *Finder) { +void RedundantFunctionPtrDereferenceCheck::registerMatchers( + MatchFinder *Finder) { Finder->addMatcher( traverse(TK_AsIs, unaryOperator(hasOperatorName("*"), has(implicitCastExpr(hasCastKind( @@ -22,7 +23,8 @@ void RedundantFunctionPtrDereferenceCheck::registerMatchers(MatchFinder *Finder) this); } -void RedundantFunctionPtrDereferenceCheck::check(const MatchFinder::MatchResult &Result) { +void RedundantFunctionPtrDereferenceCheck::check( + const MatchFinder::MatchResult &Result) { const auto *Operator = Result.Nodes.getNodeAs("op"); diag(Operator->getOperatorLoc(), "redundant repeated dereference of function pointer") diff --git a/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h b/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h index fdcf821bcf60..a04e9c165bc0 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h +++ b/clang-tools-extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h @@ -19,7 +19,8 @@ namespace clang::tidy::readability { /// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-function-ptr-dereference.html class RedundantFunctionPtrDereferenceCheck : public ClangTidyCheck { public: - RedundantFunctionPtrDereferenceCheck(StringRef Name, ClangTidyContext *Context) + RedundantFunctionPtrDereferenceCheck(StringRef Name, + ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} 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/RedundantInlineSpecifierCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp index 1693e5c5e9cd..969bf2d1add6 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp @@ -38,8 +38,8 @@ AST_POLYMORPHIC_MATCHER(isInlineSpecified, AST_POLYMORPHIC_MATCHER_P(isInternalLinkage, AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, VarDecl), - bool, strictMode) { - if (!strictMode) + bool, StrictMode) { + if (!StrictMode) return false; if (const auto *FD = dyn_cast(&Node)) return FD->getStorageClass() == SC_Static || FD->isInAnonymousNamespace(); diff --git a/clang-tools-extra/clang-tidy/readability/RedundantStringCStrCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantStringCStrCheck.cpp index 57f13db07802..c90d1521e6b8 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantStringCStrCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantStringCStrCheck.cpp @@ -52,12 +52,11 @@ void RedundantStringCStrCheck::registerMatchers( const auto StringConstructorExpr = expr(anyOf( cxxConstructExpr(argumentCountIs(1), hasDeclaration(cxxMethodDecl(hasName("basic_string")))), - cxxConstructExpr( - argumentCountIs(2), - hasDeclaration(cxxMethodDecl(hasName("basic_string"))), - // If present, the second argument is the alloc object which must not - // be present explicitly. - hasArgument(1, cxxDefaultArgExpr())))); + cxxConstructExpr(argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + // If present, the second argument is the alloc object + // which must not be present explicitly. + hasArgument(1, cxxDefaultArgExpr())))); // Match string constructor. const auto StringViewConstructorExpr = cxxConstructExpr( @@ -105,8 +104,9 @@ void RedundantStringCStrCheck::registerMatchers( // Detect: 'dst.append(str.c_str())' -> 'dst.append(str)' Finder->addMatcher( - cxxMemberCallExpr(on(StringExpr), callee(decl(cxxMethodDecl(hasAnyName( - "append", "assign", "compare")))), + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl( + hasAnyName("append", "assign", "compare")))), argumentCountIs(1), hasArgument(0, StringCStrCallExpr)), this); diff --git a/clang-tools-extra/clang-tidy/readability/SimplifySubscriptExprCheck.h b/clang-tools-extra/clang-tidy/readability/SimplifySubscriptExprCheck.h index 83bab16b2f7a..deffb09f5db2 100644 --- a/clang-tools-extra/clang-tidy/readability/SimplifySubscriptExprCheck.h +++ b/clang-tools-extra/clang-tidy/readability/SimplifySubscriptExprCheck.h @@ -25,7 +25,7 @@ class SimplifySubscriptExprCheck : public ClangTidyCheck { } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - void storeOptions(ClangTidyOptions::OptionMap& Opts) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; std::optional getCheckTraversalKind() const override { return TK_IgnoreUnlessSpelledInSource; } diff --git a/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.cpp b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.cpp new file mode 100644 index 000000000000..334b69755db2 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.cpp @@ -0,0 +1,160 @@ +//===--- UseNumericLimitsCheck.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 "UseNumericLimitsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Preprocessor.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +UseNumericLimitsCheck::UseNumericLimitsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + SignedConstants{ + {std::numeric_limits::min(), + "std::numeric_limits::min()"}, + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + {std::numeric_limits::min(), + "std::numeric_limits::min()"}, + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + {std::numeric_limits::min(), + "std::numeric_limits::min()"}, + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + {std::numeric_limits::min(), + "std::numeric_limits::min()"}, + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + }, + UnsignedConstants{ + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + {std::numeric_limits::max(), + "std::numeric_limits::max()"}, + }, + Inserter(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::IS_LLVM), + areDiagsSelfContained()) {} + +void UseNumericLimitsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", Inserter.getStyle()); +} + +void UseNumericLimitsCheck::registerMatchers(MatchFinder *Finder) { + auto PositiveIntegerMatcher = [](auto Value) { + return unaryOperator(hasOperatorName("+"), + hasUnaryOperand(integerLiteral(equals(Value)) + .bind("positive-integer-literal"))) + .bind("unary-op"); + }; + + auto NegativeIntegerMatcher = [](auto Value) { + return unaryOperator(hasOperatorName("-"), + hasUnaryOperand(integerLiteral(equals(-Value)) + .bind("negative-integer-literal"))) + .bind("unary-op"); + }; + + auto BareIntegerMatcher = [](auto Value) { + return integerLiteral(allOf(unless(hasParent(unaryOperator( + hasAnyOperatorName("-", "+")))), + equals(Value))) + .bind("bare-integer-literal"); + }; + + for (const auto &[Value, _] : SignedConstants) { + if (Value < 0) { + Finder->addMatcher(NegativeIntegerMatcher(Value), this); + } else { + Finder->addMatcher( + expr(anyOf(PositiveIntegerMatcher(Value), BareIntegerMatcher(Value))), + this); + } + } + + for (const auto &[Value, _] : UnsignedConstants) { + Finder->addMatcher( + expr(anyOf(PositiveIntegerMatcher(Value), BareIntegerMatcher(Value))), + this); + } +} + +void UseNumericLimitsCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + Inserter.registerPreprocessor(PP); +} + +void UseNumericLimitsCheck::check(const MatchFinder::MatchResult &Result) { + const IntegerLiteral *MatchedDecl = nullptr; + + const IntegerLiteral *NegativeMatchedDecl = + Result.Nodes.getNodeAs("negative-integer-literal"); + const IntegerLiteral *PositiveMatchedDecl = + Result.Nodes.getNodeAs("positive-integer-literal"); + const IntegerLiteral *BareMatchedDecl = + Result.Nodes.getNodeAs("bare-integer-literal"); + + if (NegativeMatchedDecl != nullptr) + MatchedDecl = NegativeMatchedDecl; + else if (PositiveMatchedDecl != nullptr) + MatchedDecl = PositiveMatchedDecl; + else if (BareMatchedDecl != nullptr) + MatchedDecl = BareMatchedDecl; + + const llvm::APInt MatchedIntegerConstant = MatchedDecl->getValue(); + + auto Fixer = [&](auto SourceValue, auto Value, + const std::string &Replacement) { + static_assert(std::is_same_v, + "The types of SourceValue and Value must match"); + + SourceLocation Location = MatchedDecl->getExprLoc(); + SourceRange Range{MatchedDecl->getBeginLoc(), MatchedDecl->getEndLoc()}; + + // Only valid if unary operator is present + const UnaryOperator *UnaryOpExpr = + Result.Nodes.getNodeAs("unary-op"); + + if (MatchedDecl == NegativeMatchedDecl && -SourceValue == Value) { + Range = SourceRange(UnaryOpExpr->getBeginLoc(), UnaryOpExpr->getEndLoc()); + Location = UnaryOpExpr->getExprLoc(); + SourceValue = -SourceValue; + } else if (MatchedDecl == PositiveMatchedDecl && SourceValue == Value) { + Range = SourceRange(UnaryOpExpr->getBeginLoc(), UnaryOpExpr->getEndLoc()); + Location = UnaryOpExpr->getExprLoc(); + } else if (MatchedDecl != BareMatchedDecl || SourceValue != Value) { + return; + } + + diag(Location, + "the constant '%0' is being utilized; consider using '%1' instead") + << SourceValue << Replacement + << FixItHint::CreateReplacement(Range, Replacement) + << Inserter.createIncludeInsertion( + Result.SourceManager->getFileID(Location), ""); + }; + + for (const auto &[Value, Replacement] : SignedConstants) + Fixer(MatchedIntegerConstant.getSExtValue(), Value, Replacement); + + for (const auto &[Value, Replacement] : UnsignedConstants) + Fixer(MatchedIntegerConstant.getZExtValue(), Value, Replacement); +} + +} // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.h b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.h new file mode 100644 index 000000000000..0e7e9abb8562 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/UseNumericLimitsCheck.h @@ -0,0 +1,38 @@ +//===--- UseNumericLimitsCheck.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_READABILITY_USENUMERICLIMITSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H + +#include "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" + +namespace clang::tidy::readability { + +/// Finds certain integer literals and suggests replacing them with equivalent +/// ``std::numeric_limits`` calls. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/use-numeric-limits.html +class UseNumericLimitsCheck : public ClangTidyCheck { +public: + UseNumericLimitsCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const llvm::SmallVector> SignedConstants; + const llvm::SmallVector> UnsignedConstants; + utils::IncludeInserter Inserter; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp index 7c3aee29c146..4336c723bd7c 100644 --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -337,8 +337,7 @@ Allow empty enabled checks. This suppresses the "no checks enabled" error when disabling all of the checks. )"), - cl::init(false), - cl::cat(ClangTidyCategory)); + cl::init(false), cl::cat(ClangTidyCategory)); namespace clang::tidy { @@ -370,8 +369,8 @@ static void printStats(const ClangTidyStats &Stats) { } } -static std::unique_ptr createOptionsProvider( - llvm::IntrusiveRefCntPtr FS) { +static std::unique_ptr +createOptionsProvider(llvm::IntrusiveRefCntPtr FS) { ClangTidyGlobalOptions GlobalOptions; if (std::error_code Err = parseLineFilter(LineFilter, GlobalOptions)) { llvm::errs() << "Invalid LineFilter: " << Err.message() << "\n\nUsage:\n"; @@ -448,7 +447,7 @@ static std::unique_ptr createOptionsProvider( std::move(OverrideOptions), std::move(FS)); } -llvm::IntrusiveRefCntPtr +static llvm::IntrusiveRefCntPtr getVfsFromFile(const std::string &OverlayFile, llvm::IntrusiveRefCntPtr BaseFS) { llvm::ErrorOr> Buffer = diff --git a/clang-tools-extra/clang-tidy/utils/IncludeSorter.cpp b/clang-tools-extra/clang-tidy/utils/IncludeSorter.cpp index 83248e131948..db1ea1bb514f 100644 --- a/clang-tools-extra/clang-tidy/utils/IncludeSorter.cpp +++ b/clang-tools-extra/clang-tidy/utils/IncludeSorter.cpp @@ -48,8 +48,7 @@ StringRef makeCanonicalName(StringRef Str, IncludeSorter::IncludeStyle Style) { if (StartIndex == StringRef::npos) { StartIndex = 0; } - return Canonical.substr( - 0, Canonical.find_first_of('+', StartIndex)); + return Canonical.substr(0, Canonical.find_first_of('+', StartIndex)); } return removeFirstSuffix( removeFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), diff --git a/clang-tools-extra/clang-tidy/utils/NamespaceAliaser.cpp b/clang-tools-extra/clang-tidy/utils/NamespaceAliaser.cpp index 4703ce126981..22cf23fb2446 100644 --- a/clang-tools-extra/clang-tidy/utils/NamespaceAliaser.cpp +++ b/clang-tools-extra/clang-tidy/utils/NamespaceAliaser.cpp @@ -16,15 +16,16 @@ namespace clang::tidy::utils { using namespace ast_matchers; +namespace { +AST_MATCHER_P(NamespaceAliasDecl, hasTargetNamespace, + ast_matchers::internal::Matcher, InnerMatcher) { + return InnerMatcher.matches(*Node.getNamespace(), Finder, Builder); +} +} // namespace NamespaceAliaser::NamespaceAliaser(const SourceManager &SourceMgr) : SourceMgr(SourceMgr) {} -AST_MATCHER_P(NamespaceAliasDecl, hasTargetNamespace, - ast_matchers::internal::Matcher, innerMatcher) { - return innerMatcher.matches(*Node.getNamespace(), Finder, Builder); -} - std::optional NamespaceAliaser::createAlias(ASTContext &Context, const Stmt &Statement, StringRef Namespace, diff --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp index a40433e38a04..7d84a4a9331b 100644 --- a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp +++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp @@ -30,7 +30,7 @@ static void verifyRule(const RewriteRuleWith &Rule) { // If a string unintentionally containing '%' is passed as a diagnostic, Clang // will claim the string is ill-formed and assert-fail. This function escapes // such strings so they can be safely used in diagnostics. -std::string escapeForDiagnostic(std::string ToEscape) { +static std::string escapeForDiagnostic(std::string ToEscape) { // Optimize for the common case that the string does not contain `%` at the // cost of an extra scan over the string in the slow case. auto Pos = ToEscape.find('%'); diff --git a/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp b/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp index 68574d22d15f..44db0c2aed60 100644 --- a/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp +++ b/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp @@ -41,8 +41,7 @@ std::optional isExpensiveToCopy(QualType Type, return std::nullopt; return !Type.isTriviallyCopyableType(Context) && !classHasTrivialCopyAndDestroy(Type) && - !hasDeletedCopyConstructor(Type) && - !Type->isObjCLifetimeType(); + !hasDeletedCopyConstructor(Type) && !Type->isObjCLifetimeType(); } bool recordIsTriviallyDefaultConstructible(const RecordDecl &RecordDecl, diff --git a/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.cpp b/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.cpp index bb2976081a69..bb2c71913193 100644 --- a/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.cpp +++ b/clang-tools-extra/clang-tidy/zircon/TemporaryObjectsCheck.cpp @@ -17,11 +17,15 @@ using namespace clang::ast_matchers; namespace clang::tidy::zircon { +namespace { + AST_MATCHER_P(CXXRecordDecl, matchesAnyName, ArrayRef, Names) { std::string QualifiedName = Node.getQualifiedNameAsString(); return llvm::is_contained(Names, QualifiedName); } +} // namespace + void TemporaryObjectsCheck::registerMatchers(MatchFinder *Finder) { // Matcher for default constructors. Finder->addMatcher( diff --git a/clang-tools-extra/clang-tidy/zircon/ZirconTidyModule.cpp b/clang-tools-extra/clang-tidy/zircon/ZirconTidyModule.cpp index 000680455d61..0eb5683a94e4 100644 --- a/clang-tools-extra/clang-tidy/zircon/ZirconTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/zircon/ZirconTidyModule.cpp @@ -32,6 +32,6 @@ static ClangTidyModuleRegistry::Add // This anchor is used to force the linker to link in the generated object file // and thus register the ZirconModule. -volatile int ZirconModuleAnchorSource = 0; +volatile int ZirconModuleAnchorSource = 0; // NOLINT(misc-use-internal-linkage) } // namespace clang::tidy diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 29321f7cd3fa..a703009e2b46 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -494,9 +494,9 @@ static std::vector semanticTokenModifiers() { void ClangdLSPServer::onInitialize(const InitializeParams &Params, Callback Reply) { // Determine character encoding first as it affects constructed ClangdServer. - if (Params.capabilities.offsetEncoding && !Opts.Encoding) { + if (Params.capabilities.PositionEncodings && !Opts.Encoding) { Opts.Encoding = OffsetEncoding::UTF16; // fallback - for (OffsetEncoding Supported : *Params.capabilities.offsetEncoding) + for (OffsetEncoding Supported : *Params.capabilities.PositionEncodings) if (Supported != OffsetEncoding::UnsupportedEncoding) { Opts.Encoding = Supported; break; @@ -686,6 +686,9 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params, ServerCaps["executeCommandProvider"] = llvm::json::Object{{"commands", Commands}}; + if (Opts.Encoding) + ServerCaps["positionEncoding"] = *Opts.Encoding; + llvm::json::Object Result{ {{"serverInfo", llvm::json::Object{ @@ -693,6 +696,9 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params, {"version", llvm::formatv("{0} {1} {2}", versionString(), featureString(), platformString())}}}, {"capabilities", std::move(ServerCaps)}}}; + + // TODO: offsetEncoding capability is a deprecated clangd extension and should + // be deleted. if (Opts.Encoding) Result["offsetEncoding"] = *Opts.Encoding; Reply(std::move(Result)); diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp index dc4c8fc498b1..382ea3ffe342 100644 --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -117,7 +117,9 @@ bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, std::vector generateMissingIncludeDiagnostics( ParsedAST &AST, llvm::ArrayRef MissingIncludes, - llvm::StringRef Code, HeaderFilter IgnoreHeaders, const ThreadsafeFS &TFS) { + llvm::StringRef Code, HeaderFilter IgnoreHeaders, + HeaderFilter AngledHeaders, HeaderFilter QuotedHeaders, + const ThreadsafeFS &TFS) { std::vector Result; const SourceManager &SM = AST.getSourceManager(); const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); @@ -141,7 +143,18 @@ std::vector generateMissingIncludeDiagnostics( AST.getPreprocessor().getHeaderSearchInfo(), MainFile}); llvm::StringRef HeaderRef{Spelling}; + bool Angled = HeaderRef.starts_with("<"); + if (SymbolWithMissingInclude.Providers.front().kind() == + include_cleaner::Header::Kind::Physical) { + for (auto &Filter : Angled ? QuotedHeaders : AngledHeaders) { + if (Filter(ResolvedPath)) { + Angled = !Angled; + break; + } + } + } + // We might suggest insertion of an existing include in edge cases, e.g., // include is present in a PP-disabled region, or spelling of the header // turns out to be the same as one of the unresolved includes in the @@ -151,6 +164,11 @@ std::vector generateMissingIncludeDiagnostics( if (!Replacement.has_value()) continue; + if (Angled != (Spelling.front() == '<')) { + Spelling.front() = Angled ? '<' : '"'; + Spelling.back() = Angled ? '>' : '"'; + } + Diag &D = Result.emplace_back(); D.Message = llvm::formatv("No header providing \"{0}\" is directly included", @@ -481,18 +499,19 @@ bool isPreferredProvider(const Inclusion &Inc, return false; // no header provides the symbol } -std::vector -issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, - const IncludeCleanerFindings &Findings, - const ThreadsafeFS &TFS, - HeaderFilter IgnoreHeaders) { +std::vector issueIncludeCleanerDiagnostics( + ParsedAST &AST, llvm::StringRef Code, + const IncludeCleanerFindings &Findings, const ThreadsafeFS &TFS, + HeaderFilter IgnoreHeaders, HeaderFilter AngledHeaders, + HeaderFilter QuotedHeaders) { trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics"); std::vector UnusedIncludes = generateUnusedIncludeDiagnostics( AST.tuPath(), Findings.UnusedIncludes, Code, IgnoreHeaders); std::optional RemoveAllUnused = removeAllUnusedIncludes(UnusedIncludes); std::vector MissingIncludeDiags = generateMissingIncludeDiagnostics( - AST, Findings.MissingIncludes, Code, IgnoreHeaders, TFS); + AST, Findings.MissingIncludes, Code, IgnoreHeaders, AngledHeaders, + QuotedHeaders, TFS); std::optional AddAllMissing = addAllMissingIncludes(MissingIncludeDiags); std::optional FixAll; diff --git a/clang-tools-extra/clangd/IncludeCleaner.h b/clang-tools-extra/clangd/IncludeCleaner.h index 3f6e3b2fd45b..9439eeed6a3e 100644 --- a/clang-tools-extra/clangd/IncludeCleaner.h +++ b/clang-tools-extra/clangd/IncludeCleaner.h @@ -57,11 +57,11 @@ IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST, bool AnalyzeAngledIncludes = false); -std::vector -issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, - const IncludeCleanerFindings &Findings, - const ThreadsafeFS &TFS, - HeaderFilter IgnoreHeader = {}); +std::vector issueIncludeCleanerDiagnostics( + ParsedAST &AST, llvm::StringRef Code, + const IncludeCleanerFindings &Findings, const ThreadsafeFS &TFS, + HeaderFilter IgnoreHeader = {}, HeaderFilter AngledHeaders = {}, + HeaderFilter QuotedHeaders = {}); /// Converts the clangd include representation to include-cleaner /// include representation. diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp index 389356959147..856b1fd432bb 100644 --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -33,6 +33,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" +#include "llvm/ADT/identity.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" @@ -375,7 +376,11 @@ static FunctionProtoTypeLoc getPrototypeLoc(Expr *Fn) { } if (auto F = Target.getAs()) { - return F; + // In some edge cases the AST can contain a "trivial" FunctionProtoTypeLoc + // which has null parameters. Avoid these as they don't contain useful + // information. + if (llvm::all_of(F.getParams(), llvm::identity())) + return F; } return {}; diff --git a/clang-tools-extra/clangd/ModulesBuilder.cpp b/clang-tools-extra/clangd/ModulesBuilder.cpp index bf77f43bd28b..d88aa01aad05 100644 --- a/clang-tools-extra/clangd/ModulesBuilder.cpp +++ b/clang-tools-extra/clangd/ModulesBuilder.cpp @@ -430,10 +430,10 @@ class CachingProjectModules : public ProjectModules { /// Collect the directly and indirectly required module names for \param /// ModuleName in topological order. The \param ModuleName is guaranteed to /// be the last element in \param ModuleNames. -llvm::SmallVector getAllRequiredModules(PathRef RequiredSource, - CachingProjectModules &MDB, - StringRef ModuleName) { - llvm::SmallVector ModuleNames; +llvm::SmallVector getAllRequiredModules(PathRef RequiredSource, + CachingProjectModules &MDB, + StringRef ModuleName) { + llvm::SmallVector ModuleNames; llvm::StringSet<> ModuleNamesSet; auto VisitDeps = [&](StringRef ModuleName, auto Visitor) -> void { @@ -444,7 +444,7 @@ llvm::SmallVector getAllRequiredModules(PathRef RequiredSource, if (ModuleNamesSet.insert(RequiredModuleName).second) Visitor(RequiredModuleName, Visitor); - ModuleNames.push_back(ModuleName); + ModuleNames.push_back(ModuleName.str()); }; VisitDeps(ModuleName, VisitDeps); @@ -494,13 +494,13 @@ llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile( // Get Required modules in topological order. auto ReqModuleNames = getAllRequiredModules(RequiredSource, MDB, ModuleName); for (llvm::StringRef ReqModuleName : ReqModuleNames) { - if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName)) + if (BuiltModuleFiles.isModuleUnitBuilt(ReqModuleName)) continue; if (auto Cached = Cache.getModule(ReqModuleName)) { if (IsModuleFileUpToDate(Cached->getModuleFilePath(), BuiltModuleFiles, TFS.view(std::nullopt))) { - log("Reusing module {0} from {1}", ModuleName, + log("Reusing module {0} from {1}", ReqModuleName, Cached->getModuleFilePath()); BuiltModuleFiles.addModuleFile(std::move(Cached)); continue; @@ -508,14 +508,16 @@ llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile( Cache.remove(ReqModuleName); } + std::string ReqFileName = + MDB.getSourceForModuleName(ReqModuleName, RequiredSource); llvm::Expected MF = buildModuleFile( - ModuleName, ModuleUnitFileName, getCDB(), TFS, BuiltModuleFiles); + ReqModuleName, ReqFileName, getCDB(), TFS, BuiltModuleFiles); if (llvm::Error Err = MF.takeError()) return Err; - log("Built module {0} to {1}", ModuleName, MF->getModuleFilePath()); + log("Built module {0} to {1}", ReqModuleName, MF->getModuleFilePath()); auto BuiltModuleFile = std::make_shared(std::move(*MF)); - Cache.add(ModuleName, BuiltModuleFile); + Cache.add(ReqModuleName, BuiltModuleFile); BuiltModuleFiles.addModuleFile(std::move(BuiltModuleFile)); } diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp index 9e1f6bb97722..48896e5f4ff8 100644 --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -381,8 +381,9 @@ std::vector getIncludeCleanerDiags(ParsedAST &AST, llvm::StringRef Code, Findings.MissingIncludes.clear(); if (SuppressUnused) Findings.UnusedIncludes.clear(); - return issueIncludeCleanerDiagnostics(AST, Code, Findings, TFS, - Cfg.Diagnostics.Includes.IgnoreHeader); + return issueIncludeCleanerDiagnostics( + AST, Code, Findings, TFS, Cfg.Diagnostics.Includes.IgnoreHeader, + Cfg.Style.AngledHeaders, Cfg.Style.QuotedHeaders); } tidy::ClangTidyCheckFactories diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index c9e8a175b5d7..2c858e28fa24 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -497,10 +497,19 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R, if (auto Cancel = StaleRequestSupport->getBoolean("cancel")) R.CancelsStaleRequests = *Cancel; } + if (auto *PositionEncodings = General->get("positionEncodings")) { + R.PositionEncodings.emplace(); + if (!fromJSON(*PositionEncodings, *R.PositionEncodings, + P.field("general").field("positionEncodings"))) + return false; + } } if (auto *OffsetEncoding = O->get("offsetEncoding")) { - R.offsetEncoding.emplace(); - if (!fromJSON(*OffsetEncoding, *R.offsetEncoding, + R.PositionEncodings.emplace(); + elog("offsetEncoding capability is a deprecated clangd extension that'll " + "go away with clangd 23. Migrate to standard positionEncodings " + "capability introduced by LSP 3.17"); + if (!fromJSON(*OffsetEncoding, *R.PositionEncodings, P.field("offsetEncoding"))) return false; } @@ -536,8 +545,11 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R, } } if (auto *OffsetEncoding = Experimental->get("offsetEncoding")) { - R.offsetEncoding.emplace(); - if (!fromJSON(*OffsetEncoding, *R.offsetEncoding, + R.PositionEncodings.emplace(); + elog("offsetEncoding capability is a deprecated clangd extension that'll " + "go away with clangd 23. Migrate to standard positionEncodings " + "capability introduced by LSP 3.17"); + if (!fromJSON(*OffsetEncoding, *R.PositionEncodings, P.field("offsetEncoding"))) return false; } diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 8a7809d6677e..3a6bf155ee15 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -528,8 +528,9 @@ struct ClientCapabilities { /// textDocument.semanticHighlightingCapabilities.semanticHighlighting bool TheiaSemanticHighlighting = false; - /// Supported encodings for LSP character offsets. (clangd extension). - std::optional> offsetEncoding; + /// Supported encodings for LSP character offsets. + /// general.positionEncodings + std::optional> PositionEncodings; /// The content format that should be used for Hover requests. /// textDocument.hover.contentEncoding diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index d9b73b83e902..c56375b1a98d 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -1308,7 +1308,7 @@ getMappedRanges(ArrayRef Indexed, ArrayRef Lexed) { return std::nullopt; } // Fast check for the special subset case. - if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end())) + if (llvm::includes(Indexed, Lexed)) return Lexed.vec(); std::vector Best; diff --git a/clang-tools-extra/clangd/test/module_dependencies.test b/clang-tools-extra/clangd/test/module_dependencies.test new file mode 100644 index 000000000000..79306a73da43 --- /dev/null +++ b/clang-tools-extra/clangd/test/module_dependencies.test @@ -0,0 +1,95 @@ +# A smoke test to check that a simple dependency chain for modules can work. +# +# FIXME: The test fails on Windows; see comments on https://github.com/llvm/llvm-project/pull/142828 +# UNSUPPORTED: system-windows +# +# RUN: rm -fr %t +# RUN: mkdir -p %t +# RUN: split-file %s %t +# +# RUN: sed -e "s|DIR|%/t|g" %t/compile_commands.json.tmpl > %t/compile_commands.json.tmp +# RUN: sed -e "s|CLANG_CC|%clang|g" %t/compile_commands.json.tmp > %t/compile_commands.json +# RUN: sed -e "s|DIR|%/t|g" %t/definition.jsonrpc.tmpl > %t/definition.jsonrpc.tmp +# +# On Windows, we need the URI in didOpen to look like "uri":"file:///C:/..." +# (with the extra slash in the front), so we add it here. +# RUN: sed -E -e 's|"file://([A-Z]):/|"file:///\1:/|g' %/t/definition.jsonrpc.tmp > %/t/definition.jsonrpc +# +# RUN: clangd -experimental-modules-support -lit-test < %t/definition.jsonrpc \ +# RUN: | FileCheck -strict-whitespace %t/definition.jsonrpc + +#--- A-frag.cppm +export module A:frag; +export void printA() {} + +#--- A.cppm +export module A; +export import :frag; + +#--- Use.cpp +import A; +void foo() { + print +} + +#--- compile_commands.json.tmpl +[ + { + "directory": "DIR", + "command": "CLANG_CC -fprebuilt-module-path=DIR -std=c++20 -o DIR/main.cpp.o -c DIR/Use.cpp", + "file": "DIR/Use.cpp" + }, + { + "directory": "DIR", + "command": "CLANG_CC -std=c++20 DIR/A.cppm --precompile -o DIR/A.pcm", + "file": "DIR/A.cppm" + }, + { + "directory": "DIR", + "command": "CLANG_CC -std=c++20 DIR/A-frag.cppm --precompile -o DIR/A-frag.pcm", + "file": "DIR/A-frag.cppm" + } +] + +#--- 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": "import A;\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/test/positionencoding.test b/clang-tools-extra/clangd/test/positionencoding.test new file mode 100644 index 000000000000..eea7a1a596e9 --- /dev/null +++ b/clang-tools-extra/clangd/test/positionencoding.test @@ -0,0 +1,32 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# This test verifies that we can negotiate UTF-8 offsets via the positionEncodings capability introduced in LSP 3.17. +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"general":{"positionEncodings":["utf-8","utf-16"]}},"trace":"off"}} +# CHECK: "positionEncoding": "utf-8" +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"/*ö*/int x;\nint y=x;"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":1,"character":6}}} +# /*ö*/int x; +# 01234567890 +# x is character (and utf-16) range [9,10) but byte range [10,11). +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 11, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 10, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] +--- +{"jsonrpc":"2.0","id":10000,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp index 4bd256d6be22..b11d194da04d 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -913,7 +913,6 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var if (!ResourceDir.empty()) Opts.ResourceDir = ResourceDir; Opts.BuildDynamicSymbolIndex = true; - std::vector> IdxStack; #if CLANGD_ENABLE_REMOTE if (RemoteIndexAddress.empty() != ProjectRoot.empty()) { llvm::errs() << "remote-index-address and project-path have to be " diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 69f6df46c87c..775278ccf694 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -974,7 +974,7 @@ class Foo final {})cpp"; HI.Name = "abc"; HI.Kind = index::SymbolKind::Variable; HI.NamespaceScope = ""; - HI.Definition = "int abc = ()"; + HI.Definition = "int abc"; HI.Type = "int"; HI.AccessSpecifier = "public"; }}, diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp index 0ee748c1ed2d..ec733cbe9c42 100644 --- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp +++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp @@ -220,13 +220,16 @@ TEST(IncludeCleaner, ComputeMissingHeaders) { TEST(IncludeCleaner, GenerateMissingHeaderDiags) { Annotations MainFile(R"cpp( #include "a.h" +#include "angled_wrapper.h" #include "all.h" $insert_b[[]]#include "baz.h" #include "dir/c.h" $insert_d[[]]$insert_foo[[]]#include "fuzz.h" #include "header.h" -$insert_foobar[[]]#include -$insert_f[[]]$insert_vector[[]] +$insert_foobar[[]]$insert_quoted[[]]$insert_quoted2[[]]#include "quoted_wrapper.h" +$insert_angled[[]]#include +$insert_f[[]]#include +$insert_vector[[]] #define DEF(X) const Foo *X; #define BAZ(X) const X x @@ -237,6 +240,9 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { void foo() { $b[[b]](); + $angled[[angled]](); + $quoted[[quoted]](); + $quoted2[[quoted2]](); ns::$bar[[Bar]] bar; bar.d(); @@ -263,12 +269,22 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { TU.AdditionalFiles["a.h"] = guard("#include \"b.h\""); TU.AdditionalFiles["b.h"] = guard("void b();"); + TU.AdditionalFiles["angled_wrapper.h"] = guard("#include "); + TU.AdditionalFiles["angled.h"] = guard("void angled();"); + TU.ExtraArgs.push_back("-I" + testPath(".")); + + TU.AdditionalFiles["quoted_wrapper.h"] = guard("#include \"quoted.h\""); + TU.AdditionalFiles["quoted.h"] = guard("void quoted();"); + TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\""); TU.AdditionalFiles["dir/d.h"] = guard("namespace ns { struct Bar { void d(); }; }"); TU.AdditionalFiles["system/e.h"] = guard("#include "); TU.AdditionalFiles["system/f.h"] = guard("void f();"); + TU.AdditionalFiles["system/quoted2_wrapper.h"] = + guard("#include "); + TU.AdditionalFiles["system/quoted2.h"] = guard("void quoted2();"); TU.ExtraArgs.push_back("-isystem" + testPath("system")); TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\""); @@ -297,7 +313,15 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { Findings.UnusedIncludes.clear(); std::vector Diags = issueIncludeCleanerDiagnostics( AST, TU.Code, Findings, MockFS(), - {[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }}); + /*IgnoreHeaders=*/{[](llvm::StringRef Header) { + return Header.ends_with("buzz.h"); + }}, + /*AngledHeaders=*/{[](llvm::StringRef Header) { + return Header.contains("angled.h"); + }}, + /*QuotedHeaders=*/{[](llvm::StringRef Header) { + return Header.contains("quoted.h") || Header.contains("quoted2.h"); + }}); EXPECT_THAT( Diags, UnorderedElementsAre( @@ -306,6 +330,23 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n", "#include \"b.h\""), FixMessage("add all missing includes")})), + AllOf(Diag(MainFile.range("angled"), + "No header providing \"angled\" is directly included"), + withFix({Fix(MainFile.range("insert_angled"), + "#include \n", "#include "), + FixMessage("add all missing includes")})), + AllOf( + Diag(MainFile.range("quoted"), + "No header providing \"quoted\" is directly included"), + withFix({Fix(MainFile.range("insert_quoted"), + "#include \"quoted.h\"\n", "#include \"quoted.h\""), + FixMessage("add all missing includes")})), + AllOf(Diag(MainFile.range("quoted2"), + "No header providing \"quoted2\" is directly included"), + withFix( + {Fix(MainFile.range("insert_quoted2"), + "#include \"quoted2.h\"\n", "#include \"quoted2.h\""), + FixMessage("add all missing includes")})), AllOf(Diag(MainFile.range("bar"), "No header providing \"ns::Bar\" is directly included"), withFix({Fix(MainFile.range("insert_d"), diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp index c3331d20730f..e0cd955bb1c9 100644 --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -1011,11 +1011,16 @@ TEST(ParameterHints, FunctionPointer) { f3_t f3; using f4_t = void(__stdcall *)(int param); f4_t f4; + __attribute__((noreturn)) f4_t f5; void bar() { f1($f1[[42]]); f2($f2[[42]]); f3($f3[[42]]); f4($f4[[42]]); + // This one runs into an edge case in clang's type model + // and we can't extract the parameter name. But at least + // we shouldn't crash. + f5(42); } )cpp", ExpectedHint{"param: ", "f1"}, ExpectedHint{"param: ", "f2"}, diff --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp index 03e65768c26a..ae2d98ce90d8 100644 --- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp +++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp @@ -247,13 +247,11 @@ import Dep; ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty()); // Set the mangler to filter out the invalid flag - ProjectModules->setCommandMangler( - [](tooling::CompileCommand &Command, PathRef) { - auto const It = - std::find(Command.CommandLine.begin(), Command.CommandLine.end(), - "-invalid-unknown-flag"); - Command.CommandLine.erase(It); - }); + ProjectModules->setCommandMangler([](tooling::CompileCommand &Command, + PathRef) { + auto const It = llvm::find(Command.CommandLine, "-invalid-unknown-flag"); + Command.CommandLine.erase(It); + }); // And now it returns a non-empty list of required modules since the // compilation succeeded diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index 1892f87c8e82..b04d6431f89f 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -2311,6 +2311,14 @@ TEST(FindReferences, WithinAST) { $(S::deleteObject)[[de^lete]] S; } }; + )cpp", + // Array designators + R"cpp( + const int $def[[F^oo]] = 0; + int Bar[] = { + [$(Bar)[[F^oo]]...$(Bar)[[Fo^o]] + 1] = 0, + [$(Bar)[[^Foo]] + 2] = 1 + }; )cpp"}; for (const char *Test : Tests) checkFindRefs(Test); diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index e0f81a032c38..9dede347b8c9 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -136,6 +136,12 @@ New checks Finds unintended character output from ``unsigned char`` and ``signed char`` to an ``ostream``. +- New :doc:`cppcoreguidelines-use-enum-class + ` check. + + Finds unscoped (non-class) ``enum`` declarations and suggests using + ``enum class`` instead. + - New :doc:`portability-avoid-pragma-once ` check. @@ -148,6 +154,12 @@ New checks Finds potentially erroneous calls to ``reset`` method on smart pointers when the pointee type also has a ``reset`` method. +- New :doc:`readability-use-numeric-limits + ` check. + + Finds certain integer literals and suggests replacing them with equivalent + ``std::numeric_limits`` calls. + New check aliases ^^^^^^^^^^^^^^^^^ @@ -159,6 +171,10 @@ Changes in existing checks false positives on deleted constructors that cannot be used to construct objects, even if they have public or protected access. +- Added an option to :doc:`bugprone-multi-level-implicit-pointer-conversion + ` to + choose whether to enable the check in C code or not. + - Improved :doc:`bugprone-optional-value-conversion ` check to detect conversion in argument of ``std::make_optional``. @@ -187,6 +203,16 @@ Changes in existing checks ` check by fixing a false positive where ``strerror`` was flagged as MT-unsafe. +- Improved :doc:`google-readability-namespace-comments + ` check by adding + the option `AllowOmittingNamespaceComments` to accept if a namespace comment + is omitted entirely. + +- Improved :doc:`llvm-namespace-comment + ` check by adding the option + `AllowOmittingNamespaceComments` to accept if a namespace comment is omitted + entirely. + - Improved :doc:`misc-const-correctness ` check by adding the option `AllowedTypes`, that excludes specified types from const-correctness @@ -236,6 +262,13 @@ Changes in existing checks ` check to support math functions of different precisions. +- Improved :doc:`modernize-use-trailing-return-type + ` check by adding + support to modernize lambda signatures to use trailing return type and adding + two new options: `TransformFunctions` and `TransformLambdas` to control + whether function declarations and lambdas should be transformed by the check. + Fixed false positives when lambda was matched as a function in C++11 mode. + - Improved :doc:`performance-move-const-arg ` check by fixing false negatives on ternary operators calling ``std::move``. @@ -244,6 +277,22 @@ Changes in existing checks ` check performance by tolerating fix-it breaking compilation when functions is used as pointers to avoid matching usage of functions within the current compilation unit. + Added an option `IgnoreCoroutines` with the default value `true` to + suppress this check for coroutines where passing by reference may be unsafe. + +- Improved :doc:`readability-convert-member-functions-to-static + ` check by + fixing false positives on member functions with an explicit object parameter. + +- Improved :doc:`readability-function-size + ` check by adding new option + `CountMemberInitAsStmt` that allows counting class member initializers in + constructors as statements. + +- Improved :doc:`readability-math-missing-parentheses + ` check by fixing + false negatives where math expressions are the operand of assignment operators + or comparison operators. - Improved :doc:`readability-qualified-auto ` check by adding the option diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/multi-level-implicit-pointer-conversion.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/multi-level-implicit-pointer-conversion.rst index e6dc5c13aa02..14df6bd9ccea 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/multi-level-implicit-pointer-conversion.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/multi-level-implicit-pointer-conversion.rst @@ -42,3 +42,11 @@ Additionally, it is recommended that developers thoroughly check and verify the safety of the conversion before using an explicit cast. This extra level of caution can help catch potential issues early on in the development process, improving the overall reliability and maintainability of the code. + +Options +------- + +.. option:: EnableInC + + If `true`, enables the check in C code (it is always enabled in C++ code). + Default is `true`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/return-const-ref-from-parameter.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/return-const-ref-from-parameter.rst index ba47399914de..325a0a2aa9cc 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/return-const-ref-from-parameter.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/return-const-ref-from-parameter.rst @@ -42,7 +42,7 @@ on the control flow of the function, an overload where all problematic This issue can also be resolved by adding ``[[clang::lifetimebound]]``. Clang enable ``-Wdangling`` warning by default which can detect mis-uses of the -annotated function. See `lifetimebound attribute `_ +annotated function. See `lifetimebound attribute `_ for details. .. code-block:: c++ diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/use-enum-class.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/use-enum-class.rst new file mode 100644 index 000000000000..9e9f4c99dc24 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/use-enum-class.rst @@ -0,0 +1,35 @@ +.. title:: clang-tidy - cppcoreguidelines-use-enum-class + +cppcoreguidelines-use-enum-class +================================ + +Finds unscoped (non-class) ``enum`` declarations and suggests using +``enum class`` instead. + +This check implements `Enum.3 +`_ +from the C++ Core Guidelines." + +Example: + +.. code-block:: c++ + + enum E {}; // use "enum class E {};" instead + enum class E {}; // OK + + struct S { + enum E {}; // use "enum class E {};" instead + // OK with option IgnoreUnscopedEnumsInClasses + }; + + namespace N { + enum E {}; // use "enum class E {};" instead + } + +Options +------- + +.. option:: IgnoreUnscopedEnumsInClasses + + When `true`, ignores unscoped ``enum`` declarations in classes. + Default is `false`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 5a79d61b1fd7..57ae7d330a3c 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -212,6 +212,7 @@ Clang-Tidy Checks :doc:`cppcoreguidelines-rvalue-reference-param-not-moved `, :doc:`cppcoreguidelines-slicing `, :doc:`cppcoreguidelines-special-member-functions `, + :doc:`cppcoreguidelines-use-enum-class `, :doc:`cppcoreguidelines-virtual-class-destructor `, "Yes" :doc:`darwin-avoid-spinlock `, :doc:`darwin-dispatch-once-nonstatic `, "Yes" @@ -408,6 +409,7 @@ Clang-Tidy Checks :doc:`readability-uniqueptr-delete-release `, "Yes" :doc:`readability-uppercase-literal-suffix `, "Yes" :doc:`readability-use-anyofallof `, + :doc:`readability-use-numeric-limits `, "Yes" :doc:`readability-use-std-min-max `, "Yes" :doc:`zircon-temporary-objects `, diff --git a/clang-tools-extra/docs/clang-tidy/checks/llvm/namespace-comment.rst b/clang-tools-extra/docs/clang-tidy/checks/llvm/namespace-comment.rst index be90260be73a..23673c9b4cfd 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/llvm/namespace-comment.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/namespace-comment.rst @@ -39,3 +39,9 @@ Options An unsigned integer specifying the number of spaces before the comment closing a namespace definition. Default is `1U`. + +.. option:: AllowOmittingNamespaceComments + + When `true`, the check will accept if no namespace comment is present. + The check will only fail if the specified namespace comment is different + than expected. Default is `false`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-trailing-return-type.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-trailing-return-type.rst index 0593a35326aa..2dac7cc76f41 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-trailing-return-type.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-trailing-return-type.rst @@ -3,7 +3,7 @@ modernize-use-trailing-return-type ================================== -Rewrites function signatures to use a trailing return type +Rewrites function and lambda signatures to use a trailing return type (introduced in C++11). This transformation is purely stylistic. The return type before the function name is replaced by ``auto`` and inserted after the function parameter list (and qualifiers). @@ -16,6 +16,7 @@ Example int f1(); inline int f2(int arg) noexcept; virtual float f3() const && = delete; + auto lambda = []() {}; transforms to: @@ -24,6 +25,7 @@ transforms to: auto f1() -> int; inline auto f2(int arg) -> int noexcept; virtual auto f3() const && -> float = delete; + auto lambda = []() -> void {}; Known Limitations ----------------- @@ -66,3 +68,25 @@ a careless rewrite would produce the following output: This code fails to compile because the S in the context of f refers to the equally named function parameter. Similarly, the S in the context of m refers to the equally named class member. The check can currently only detect and avoid a clash with a function parameter name. + +Options +------- + +.. option:: TransformFunctions + + When set to `true`, function declarations will be transformed to use trailing + return. Default is `true`. + +.. option:: TransformLambdas + + Controls how lambda expressions are transformed to use trailing + return type. Possible values are: + + * `all` - Transform all lambda expressions without an explicit return type + to use trailing return type. If type can not be deduced, ``auto`` will be + used since C++14 and generic message will be emitted otherwise. + * `all_except_auto` - Transform all lambda expressions except those whose return + type can not be deduced. + * `none` - Do not transform any lambda expressions. + + Default is `all`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/enum-size.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/enum-size.rst index f72b8c7eabc2..b7631139a013 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/performance/enum-size.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/performance/enum-size.rst @@ -34,7 +34,7 @@ dependent). .. code-block:: c++ // AFTER - enum Color : std:int8_t { + enum Color : std::int8_t { RED = -1, GREEN = 0, BLUE = 1 diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-value-param.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-value-param.rst index dc86530b95f1..cd25d7d94d99 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-value-param.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-value-param.rst @@ -56,7 +56,7 @@ Will become: Because the fix-it needs to change the signature of the function, it may break builds if the function is used in multiple translation units or some codes -depends on funcion signatures. +depends on function signatures. Options ------- @@ -74,3 +74,10 @@ Options default is empty. If a name in the list contains the sequence `::`, it is matched against the qualified type name (i.e. ``namespace::Type``), otherwise it is matched against only the type name (i.e. ``Type``). + +.. option:: IgnoreCoroutines + + A boolean specifying whether the check should suggest passing parameters by + reference in coroutines. Passing parameters by reference in coroutines may + not be safe, please see :doc:`cppcoreguidelines-avoid-reference-coroutine-parameters <../cppcoreguidelines/avoid-reference-coroutine-parameters>` + for more information. Default is `true`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst index 133bd3e9c8cb..253e7c483cb8 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/function-size.rst @@ -43,3 +43,8 @@ Options The default is `none` (ignore the number of variables). Please note that function parameters and variables declared in lambdas, GNU Statement Expressions, and nested class inline functions are not counted. + +.. option:: CountMemberInitAsStmt + + When `true`, count class member initializers in constructors as statements. + Default is `true`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/use-numeric-limits.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/use-numeric-limits.rst new file mode 100644 index 000000000000..0f6ca9f0cf2c --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/use-numeric-limits.rst @@ -0,0 +1,31 @@ +.. title:: clang-tidy - readability-use-numeric-limits + +readability-use-numeric-limits +============================== + +Finds certain integer literals and suggests replacing them with equivalent +``std::numeric_limits`` calls. + +Before: + +.. code-block:: c++ + + void foo() { + int32_t a = 2147483647; + } + +After: + +.. code-block:: c++ + + void foo() { + int32_t a = std::numeric_limits::max(); + } + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/clang-tools-extra/modularize/CoverageChecker.cpp b/clang-tools-extra/modularize/CoverageChecker.cpp index fe6711398ab7..1345a6ef8f48 100644 --- a/clang-tools-extra/modularize/CoverageChecker.cpp +++ b/clang-tools-extra/modularize/CoverageChecker.cpp @@ -329,10 +329,8 @@ bool CoverageChecker::collectFileSystemHeaders() { else { // Otherwise we only look at the sub-trees specified by the // include paths. - for (std::vector::const_iterator I = IncludePaths.begin(), - E = IncludePaths.end(); - I != E; ++I) { - if (!collectFileSystemHeaders(*I)) + for (const std::string &IncludePath : IncludePaths) { + if (!collectFileSystemHeaders(IncludePath)) return false; } } diff --git a/clang-tools-extra/modularize/Modularize.cpp b/clang-tools-extra/modularize/Modularize.cpp index 7f8a19280b11..2a90c5e3f678 100644 --- a/clang-tools-extra/modularize/Modularize.cpp +++ b/clang-tools-extra/modularize/Modularize.cpp @@ -339,8 +339,8 @@ static std::string findInputFile(const CommandLineArguments &CLArgs) { llvm::opt::Visibility VisibilityMask(options::CC1Option); unsigned MissingArgIndex, MissingArgCount; SmallVector Argv; - for (auto I = CLArgs.begin(), E = CLArgs.end(); I != E; ++I) - Argv.push_back(I->c_str()); + for (const std::string &CLArg : CLArgs) + Argv.push_back(CLArg.c_str()); InputArgList Args = getDriverOptTable().ParseArgs( Argv, MissingArgIndex, MissingArgCount, VisibilityMask); std::vector Inputs = Args.getAllArgValues(OPT_INPUT); diff --git a/clang-tools-extra/modularize/ModularizeUtilities.cpp b/clang-tools-extra/modularize/ModularizeUtilities.cpp index 9ad1731915a8..8a24f21d658d 100644 --- a/clang-tools-extra/modularize/ModularizeUtilities.cpp +++ b/clang-tools-extra/modularize/ModularizeUtilities.cpp @@ -69,8 +69,7 @@ ModularizeUtilities *ModularizeUtilities::createModularizeUtilities( // Load all header lists and dependencies. std::error_code ModularizeUtilities::loadAllHeaderListsAndDependencies() { // For each input file. - for (auto I = InputFilePaths.begin(), E = InputFilePaths.end(); I != E; ++I) { - llvm::StringRef InputPath = *I; + for (llvm::StringRef InputPath : InputFilePaths) { // If it's a module map. if (InputPath.ends_with(".modulemap")) { // Load the module map. diff --git a/clang-tools-extra/test/clang-doc/invalid-options.cpp b/clang-tools-extra/test/clang-doc/invalid-options.cpp index 59a0944718e3..15e098ad4c78 100644 --- a/clang-tools-extra/test/clang-doc/invalid-options.cpp +++ b/clang-tools-extra/test/clang-doc/invalid-options.cpp @@ -2,7 +2,7 @@ // RUN: rm -rf %t && touch %t // RUN: not clang-doc %s -output=%t/subdir 2>&1 | FileCheck %s --check-prefix=OUTPUT-FAIL // OUTPUT-FAIL: clang-doc error: -// OUTPUT-FAIL: {{(Not a directory|[Nn]o such file or directory)}} +// OUTPUT-FAIL-SAME: failed to create directory. /// Invalid format option. // RUN: rm -rf %t && mkdir %t && touch %t/file diff --git a/clang-tools-extra/test/clang-doc/json/class-requires.cpp b/clang-tools-extra/test/clang-doc/json/class-requires.cpp new file mode 100644 index 000000000000..2dd25771d6d8 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/class-requires.cpp @@ -0,0 +1,34 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json + +template +concept Addable = requires(T a, T b) { + { a + b }; +}; + +template +requires Addable +struct MyClass; + +// CHECK: "Name": "MyClass", +// CHECK-NEXT: "Namespace": [ +// CHECK-NEXT: "GlobalNamespace" +// CHECK-NEXT: ], +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "Template": { +// CHECK-NEXT: "Constraints": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Addable", +// CHECK-NEXT: "Name": "Addable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Addable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Parameters": [ +// CHECK-NEXT: "typename T" +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" diff --git a/clang-tools-extra/test/clang-doc/json/class-template.cpp b/clang-tools-extra/test/clang-doc/json/class-template.cpp new file mode 100644 index 000000000000..e3ca086d1d9a --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/class-template.cpp @@ -0,0 +1,29 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json + +template struct MyClass { + T MemberTemplate; + T method(T Param); +}; + +// CHECK: "Name": "MyClass", +// CHECK: "Name": "method", +// CHECK: "Params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "Param", +// CHECK-NEXT: "Type": "T" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "T", +// CHECK-NEXT: "QualName": "T" +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK: "Name": "MemberTemplate", +// CHECK: "Type": "T" +// CHECK: "Template": { +// CHECK-NEXT: "Parameters": [ +// CHECK-NEXT: "typename T" +// CHECK-NEXT: ] diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp b/clang-tools-extra/test/clang-doc/json/class.cpp new file mode 100644 index 000000000000..9ae1f14e2d2a --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/class.cpp @@ -0,0 +1,193 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json + +struct Foo; + +// This is a nice class. +// It has some nice methods and fields. +// @brief This is a brief description. +struct MyClass { + int PublicField; + + int myMethod(int MyParam); + static void staticMethod(); + const int& getConst(); + + enum Color { + RED, + GREEN, + BLUE = 5 + }; + + typedef int MyTypedef; + + class NestedClass; +protected: + int protectedMethod(); + + int ProtectedField; +}; + +// CHECK: { +// CHECK-NEXT: "Description": [ +// CHECK-NEXT: { +// CHECK-NEXT: "FullComment": { +// CHECK-NEXT: "Children": [ +// CHECK-NEXT: { +// CHECK-NEXT: "ParagraphComment": { +// CHECK-NEXT: "Children": [ +// CHECK-NEXT: { +// CHECK-NEXT: "TextComment": " This is a nice class." +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "TextComment": " It has some nice methods and fields." +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "TextComment": "" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK: { +// CHECK-NEXT: "BlockCommandComment": { +// CHECK-NEXT: "Children": [ +// CHECK-NEXT: { +// CHECK-NEXT: "ParagraphComment": { +// CHECK-NEXT: "Children": [ +// CHECK-NEXT: { +// CHECK-NEXT: "TextComment": " This is a brief description." +// CHECK-NEXT: } +// CHECK: "Command": "brief" +// CHECK: "Enums": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Location": { +// CHECK-NEXT: "Filename": "{{.*}}class.cpp", +// CHECK-NEXT: "LineNumber": 17 +// CHECK-NEXT: }, +// CHECK-NEXT: "Members": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "RED", +// CHECK-NEXT: "Value": "0" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "GREEN", +// CHECK-NEXT: "Value": "1" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "BLUE", +// CHECK-NEXT: "ValueExpr": "5" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Name": "Color", +// CHECK-NEXT: "Namespace": [ +// CHECK-NEXT: "MyClass", +// CHECK-NEXT: "GlobalNamespace" +// CHECK-NEXT: ], +// CHECK-NEXT: "Scoped": false, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// COM: FIXME: FullName is not emitted correctly. +// CHECK-NEXT: "FullName": "", +// CHECK-NEXT: "IsTypedef": false, +// CHECK-NEXT: "Location": { +// CHECK-NEXT: "Filename": "{{.*}}class.cpp", +// CHECK-NEXT: "LineNumber": 10 +// CHECK-NEXT: }, +// CHECK-NEXT: "Name": "MyClass", +// CHECK-NEXT: "Namespace": [ +// CHECK-NEXT: "GlobalNamespace" +// CHECK-NEXT: ], +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "ProtectedFunctions": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, +// CHECK-NEXT: "Name": "protectedMethod", +// CHECK-NEXT: "Namespace": [ +// CHECK-NEXT: "MyClass", +// CHECK-NEXT: "GlobalNamespace" +// CHECK-NEXT: ], +// CHECK-NEXT: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "int", +// CHECK-NEXT: "QualName": "int", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "ProtectedMembers": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "ProtectedField", +// CHECK-NEXT: "Type": "int" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "PublicFunctions": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, +// CHECK-NEXT: "Name": "myMethod", +// CHECK-NEXT: "Namespace": [ +// CHECK-NEXT: "MyClass", +// CHECK-NEXT: "GlobalNamespace" +// CHECK-NEXT: ], +// CHECK-NEXT: "Params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "MyParam", +// CHECK-NEXT: "Type": "int" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "int", +// CHECK-NEXT: "QualName": "int", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK: "IsStatic": true, +// CHECK: "Name": "getConst", +// CHECK: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "const int &", +// CHECK-NEXT: "QualName": "const int &", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK: "PublicMembers": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "PublicField", +// CHECK-NEXT: "Type": "int" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Records": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "NestedClass", +// CHECK-NEXT: "Path": "GlobalNamespace{{[\/]+}}MyClass", +// CHECK-NEXT: "QualName": "NestedClass", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "Typedefs": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsUsing": false, +// CHECK-NEXT: "Location": { +// CHECK-NEXT: "Filename": "{{.*}}class.cpp", +// CHECK-NEXT: "LineNumber": 23 +// CHECK-NEXT: }, +// CHECK-NEXT: "Name": "MyTypedef", +// CHECK-NEXT: "Namespace": [ +// CHECK-NEXT: "MyClass", +// CHECK-NEXT: "GlobalNamespace" +// CHECK-NEXT: ], +// CHECK-NEXT: "TypeDeclaration": "", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}", +// CHECK-NEXT: "Underlying": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "int", +// CHECK-NEXT: "QualName": "int", +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } diff --git a/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp b/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp new file mode 100644 index 000000000000..b49dec5cc78c --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/compound-constraints.cpp @@ -0,0 +1,121 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/index.json + +template concept Incrementable = requires (T a) { + a++; +}; + +template concept Decrementable = requires (T a) { + a--; +}; + +template concept PreIncrementable = requires (T a) { + ++a; +}; + +template concept PreDecrementable = requires (T a) { + --a; +}; + +template requires Incrementable && Decrementable void One(); + +template requires (Incrementable && Decrementable) void Two(); + +template requires (Incrementable && Decrementable) || (PreIncrementable && PreDecrementable) void Three(); + +template requires (Incrementable && Decrementable) || PreIncrementable void Four(); + +// CHECK: "Name": "One", +// CHECK: "Template": { +// CHECK-NEXT: "Constraints": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Incrementable", +// CHECK-NEXT: "Name": "Incrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Incrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Decrementable", +// CHECK-NEXT: "Name": "Decrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Decrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK: "Name": "Two", +// CHECK: "Template": { +// CHECK-NEXT: "Constraints": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Incrementable", +// CHECK-NEXT: "Name": "Incrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Incrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Decrementable", +// CHECK-NEXT: "Name": "Decrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Decrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK: "Name": "Three", +// CHECK: "Template": { +// CHECK-NEXT: "Constraints": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Incrementable", +// CHECK-NEXT: "Name": "Incrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Incrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Decrementable", +// CHECK-NEXT: "Name": "Decrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Decrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "PreIncrementable", +// CHECK-NEXT: "Name": "PreIncrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "PreIncrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "PreDecrementable", +// CHECK-NEXT: "Name": "PreDecrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "PreDecrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK: "Name": "Four", +// CHECK: "Template": { +// CHECK-NEXT: "Constraints": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Incrementable", +// CHECK-NEXT: "Name": "Incrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Incrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Decrementable", +// CHECK-NEXT: "Name": "Decrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Decrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "PreIncrementable", +// CHECK-NEXT: "Name": "PreIncrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "PreIncrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], diff --git a/clang-tools-extra/test/clang-doc/json/concept.cpp b/clang-tools-extra/test/clang-doc/json/concept.cpp new file mode 100644 index 000000000000..887c9d79146a --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/concept.cpp @@ -0,0 +1,38 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/index.json + +// Requires that T suports post and pre-incrementing. +template +concept Incrementable = requires(T x) { + ++x; + x++; +}; + +// CHECK: { +// CHECK-NEXT: "Concepts": [ +// CHECK-NEXT: { +// CHECK-NEXT: "ConstraintExpression": "requires (T x) { ++x; x++; }", +// CHECK-NEXT: "Description": [ +// CHECK-NEXT: { +// CHECK-NEXT: "FullComment": { +// CHECK-NEXT: "Children": [ +// CHECK-NEXT: { +// CHECK-NEXT: "ParagraphComment": { +// CHECK-NEXT: "Children": [ +// CHECK-NEXT: { +// CHECK-NEXT: "TextComment": " Requires that T suports post and pre-incrementing." +// CHECK: ], +// CHECK-NEXT: "IsType": true, +// CHECK-NEXT: "Name": "Incrementable", +// CHECK-NEXT: "Template": { +// CHECK-NEXT: "Parameters": [ +// CHECK-NEXT: "typename T" +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK: "Name": "", +// CHECK: "USR": "0000000000000000000000000000000000000000" +// CHECK: } diff --git a/clang-tools-extra/test/clang-doc/json/function-requires.cpp b/clang-tools-extra/test/clang-doc/json/function-requires.cpp new file mode 100644 index 000000000000..99eb2bdb898f --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/function-requires.cpp @@ -0,0 +1,79 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/index.json + +template +concept Incrementable = requires(T x) { + ++x; + x++; +}; + +template void increment(T t) requires Incrementable; + +template Incrementable auto incrementTwo(T t); + +// CHECK: "Functions": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, +// CHECK-NEXT: "Name": "increment", +// CHECK-NEXT: "Params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "t", +// CHECK-NEXT: "Type": "T" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "void", +// CHECK-NEXT: "QualName": "void", +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK-NEXT: }, +// CHECK-NEXT: "Template": { +// CHECK-NEXT: "Constraints": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Incrementable", +// CHECK-NEXT: "Name": "Incrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Incrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Parameters": [ +// CHECK-NEXT: "typename T" +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, +// CHECK-NEXT: "Name": "incrementTwo", +// CHECK-NEXT: "Params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "t", +// CHECK-NEXT: "Type": "T" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "Incrementable auto", +// CHECK-NEXT: "QualName": "Incrementable auto", +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK-NEXT: }, +// CHECK-NEXT: "Template": { +// CHECK-NEXT: "Constraints": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Expression": "Incrementable", +// CHECK-NEXT: "Name": "Incrementable", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "Incrementable", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Parameters": [ +// CHECK-NEXT: "Incrementable T" +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } diff --git a/clang-tools-extra/test/clang-doc/json/function-specifiers.cpp b/clang-tools-extra/test/clang-doc/json/function-specifiers.cpp new file mode 100644 index 000000000000..7005fb7b3e66 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/function-specifiers.cpp @@ -0,0 +1,25 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/index.json + +static void myFunction() {} + +void noExceptFunction() noexcept {} + +inline void inlineFunction() {} + +extern void externFunction() {} + +constexpr void constexprFunction() {} + +// CHECK: "Functions": [ +// CHECK-NEXT: { +// CHECK: "IsStatic": true, +// COM: FIXME: Emit ExceptionSpecificationType +// CHECK-NOT: "ExceptionSpecifcation" : "noexcept", +// COM: FIXME: Emit inline +// CHECK-NOT: "IsInline": true, +// COM: FIXME: Emit extern +// CHECK-NOT: "IsExtern": true, +// COM: FIXME: Emit constexpr +// CHECK-NOT: "IsConstexpr": true, diff --git a/clang-tools-extra/test/clang-doc/json/method-template.cpp b/clang-tools-extra/test/clang-doc/json/method-template.cpp new file mode 100644 index 000000000000..c51a2706d1c2 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/method-template.cpp @@ -0,0 +1,40 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json + +struct MyClass { + template T methodTemplate(T param) { + } +}; + +// CHECK: "PublicFunctions": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, +// CHECK-NEXT: "Location": { +// CHECK-NEXT: "Filename": "{{.*}}method-template.cpp", +// CHECK-NEXT: "LineNumber": 6 +// CHECK-NEXT: }, +// CHECK-NEXT: "Name": "methodTemplate", +// CHECK-NEXT: "Namespace": [ +// CHECK-NEXT: "MyClass", +// CHECK-NEXT: "GlobalNamespace" +// CHECK-NEXT: ], +// CHECK-NEXT: "Params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "param", +// CHECK-NEXT: "Type": "T" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "T", +// CHECK-NEXT: "QualName": "T", +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK-NEXT: }, +// CHECK-NEXT: "Template": { +// CHECK-NEXT: "Parameters": [ +// CHECK-NEXT: "class T" +// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" diff --git a/clang-tools-extra/test/clang-doc/json/namespace.cpp b/clang-tools-extra/test/clang-doc/json/namespace.cpp new file mode 100644 index 000000000000..248d47351bd3 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/namespace.cpp @@ -0,0 +1,125 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/index.json + +class MyClass {}; + +void myFunction(int Param); + +namespace NestedNamespace { +} // namespace NestedNamespace + +// FIXME: Global variables are not mapped or serialized. +static int Global; + +enum Color { + RED, + GREEN, + BLUE = 5 +}; + +typedef int MyTypedef; + +// CHECK: { +// CHECK-NEXT: "Enums": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Location": { +// CHECK-NEXT: "Filename": "{{.*}}namespace.cpp", +// CHECK-NEXT: "LineNumber": 15 +// CHECK-NEXT: }, +// CHECK-NEXT: "Members": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "RED", +// CHECK-NEXT: "Value": "0" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "GREEN", +// CHECK-NEXT: "Value": "1" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "BLUE", +// CHECK-NEXT: "ValueExpr": "5" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Name": "Color", +// CHECK-NEXT: "Scoped": false, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Functions": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, +// CHECK-NEXT: "Name": "myFunction", +// CHECK-NEXT: "Params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "Param", +// CHECK-NEXT: "Type": "int" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "void", +// CHECK-NEXT: "QualName": "void", +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Name": "", +// CHECK-NEXT: "Namespaces": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "NestedNamespace", +// CHECK-NEXT: "Path": "", +// CHECK-NEXT: "QualName": "NestedNamespace", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Records": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "MyClass", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "QualName": "MyClass", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Typedefs": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsUsing": false, +// CHECK-NEXT: "Location": { +// CHECK-NEXT: "Filename": "{{.*}}namespace.cpp", +// CHECK-NEXT: "LineNumber": 21 +// CHECK-NEXT: }, +// CHECK-NEXT: "Name": "MyTypedef", +// CHECK-NEXT: "TypeDeclaration": "", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}", +// CHECK-NEXT: "Underlying": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "int", +// CHECK-NEXT: "QualName": "int", +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000" +// CHECK-NOT: "Variables": [ +// CHECK-NOT: { +// CHECK-NOT: "IsStatic": true, +// CHECK-NOT: "Location": { +// CHECK-NOT: "Filename": "{{.*}}namespace.cpp", +// CHECK-NOT: "LineNumber": 13 +// CHECK-NOT: }, +// CHECK-NOT: "Name": "Global", +// CHECK-NOT: "Type": { +// COM: FIXME: IsBuiltIn emits as its default value +// CHECK-NOT: "IsBuiltIn": false, +// CHECK-NOT: "IsTemplate": false, +// CHECK-NOT: "Name": "int", +// CHECK-NOT: "QualName": "int", +// CHECK-NOT: "USR": "0000000000000000000000000000000000000000" +// CHECK-NOT: }, +// CHECK-NOT: "USR": "{{[0-9A-F]*}}" +// CHECK-NOT: } +// CHECK-NOT: ] +// CHECK-NEXT: } diff --git a/clang-tools-extra/test/clang-doc/mustache-index.cpp b/clang-tools-extra/test/clang-doc/mustache-index.cpp new file mode 100644 index 000000000000..cad4cc8b6931 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/mustache-index.cpp @@ -0,0 +1,75 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --format=mustache --output=%t --executor=standalone %s +// RUN: FileCheck %s < %t/GlobalNamespace/index.html + +enum Color { + RED, + BLUE, + GREEN +}; + +class Foo; + +// CHECK: +// CHECK-NEXT:
    +// CHECK-NEXT: +// CHECK-NEXT:
+// CHECK: +// CHECK-NEXT:
    +// CHECK-NEXT: +// CHECK-NEXT:
+ +// CHECK:
+// CHECK-NEXT:

Enumerations

+// CHECK-NEXT:
+// CHECK-NEXT:
+// CHECK-NEXT:
+// CHECK-NEXT:
+// CHECK-NEXT:                      
+// CHECK-NEXT:                          enum Color
+// CHECK-NEXT:                      
+// CHECK-NEXT:                  
+// CHECK-NEXT:
+// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK: +// CHECK-NEXT: +// CHECK-NEXT:
NameValue
RED0
BLUE1
GREEN2
+// CHECK-NEXT:
+// CHECK-NEXT: Defined at line 5 of file {{.*}}mustache-index.cpp +// CHECK-NEXT:
+// CHECK-NEXT:
+// CHECK-NEXT:
+// CHECK-NEXT:
+ +// CHECK:
+// CHECK-NEXT:

Inner Classes

+// CHECK-NEXT:
    +// CHECK-NEXT:
  • +// CHECK-NEXT:
    class Foo
    +// CHECK-NEXT:
  • +// CHECK-NEXT:
+// CHECK-NEXT:
diff --git a/clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp b/clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp new file mode 100644 index 000000000000..ec29b2449169 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp @@ -0,0 +1,13 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --format=mustache --output=%t --executor=standalone %s +// RUN: FileCheck %s < %t/MyNamespace/index.html + +namespace MyNamespace { + class Foo; +} + +// CHECK:
    +// CHECK-NEXT:
  • +// CHECK-NEXT:
    class Foo
    +// CHECK-NEXT:
  • +// CHECK-NEXT:
diff --git a/clang-tools-extra/test/clang-doc/templates.cpp b/clang-tools-extra/test/clang-doc/templates.cpp index 426a0b16befd..abe03a7d2d0e 100644 --- a/clang-tools-extra/test/clang-doc/templates.cpp +++ b/clang-tools-extra/test/clang-doc/templates.cpp @@ -112,22 +112,22 @@ tuple func_with_tuple_param(tuple t) { return t; // YAML-NEXT: - USR: '{{([0-9A-F]{40})}}' // YAML-NEXT: Name: 'func_with_tuple_param' // YAML-NEXT: Description: -// YAML-NEXT: - Kind: 'FullComment' +// YAML-NEXT: - Kind: FullComment // YAML-NEXT: Children: -// YAML-NEXT: - Kind: 'ParagraphComment' +// YAML-NEXT: - Kind: ParagraphComment // YAML-NEXT: Children: -// YAML-NEXT: - Kind: 'TextComment' +// YAML-NEXT: - Kind: TextComment // YAML-NEXT: Text: ' A function with a tuple parameter' -// YAML-NEXT: - Kind: 'ParagraphComment' +// YAML-NEXT: - Kind: ParagraphComment // YAML-NEXT: Children: -// YAML-NEXT: - Kind: 'TextComment' -// YAML-NEXT: - Kind: 'ParamCommandComment' +// YAML-NEXT: - Kind: TextComment +// YAML-NEXT: - Kind: ParamCommandComment // YAML-NEXT: Direction: '[in]' // YAML-NEXT: ParamName: 't' // YAML-NEXT: Children: -// YAML-NEXT: - Kind: 'ParagraphComment' +// YAML-NEXT: - Kind: ParagraphComment // YAML-NEXT: Children: -// YAML-NEXT: - Kind: 'TextComment' +// YAML-NEXT: - Kind: TextComment // YAML-NEXT: Text: ' The input to func_with_tuple_param' // YAML-NEXT: DefLocation: // YAML-NEXT: LineNumber: [[# @LINE - 23]] diff --git a/clang-tools-extra/test/clang-reorder-fields/MacroExpandsToMultipleFields.cpp b/clang-tools-extra/test/clang-reorder-fields/MacroExpandsToMultipleFields.cpp new file mode 100644 index 000000000000..5bafcd19ea82 --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/MacroExpandsToMultipleFields.cpp @@ -0,0 +1,13 @@ +// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s + +namespace bar { + +#define FIELDS_DECL int x; int y; // CHECK: {{^#define FIELDS_DECL int x; int y;}} + +// The order of fields should not change. +struct Foo { + FIELDS_DECL // CHECK: {{^ FIELDS_DECL}} + int z; // CHECK-NEXT: {{^ int z;}} +}; + +} // end namespace bar diff --git a/clang-tools-extra/test/clang-reorder-fields/MacroExpansionField.cpp b/clang-tools-extra/test/clang-reorder-fields/MacroExpansionField.cpp new file mode 100644 index 000000000000..a4c3cbc1e12f --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/MacroExpansionField.cpp @@ -0,0 +1,24 @@ +// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s + +namespace bar { + +#define INT_DECL(NAME) int NAME // CHECK: {{^#define INT_DECL\(NAME\) int NAME}} +#define MACRO_DECL int x; // CHECK-NEXT: {{^#define MACRO_DECL int x;}} + +struct Foo { + MACRO_DECL // CHECK: {{^ INT_DECL\(z\);}} + int y; // CHECK-NEXT: {{^ int y;}} + INT_DECL(z); // CHECK-NEXT: {{^ MACRO_DECL}} +}; + +#define FOO 0 // CHECK: {{^#define FOO 0}} +#define BAR 1 // CHECK-NEXT: {{^#define BAR 1}} +#define BAZ 2 // CHECK-NEXT: {{^#define BAZ 2}} + +struct Foo foo = { + FOO, // CHECK: {{^ BAZ,}} + BAR, // CHECK-NEXT: {{^ BAR,}} + BAZ, // CHECK-NEXT: {{^ FOO,}} +}; + +} // end namespace bar diff --git a/clang-tools-extra/test/clang-reorder-fields/MultipleFieldDeclsInStatement.cpp b/clang-tools-extra/test/clang-reorder-fields/MultipleFieldDeclsInStatement.cpp new file mode 100644 index 000000000000..437e7b91e27a --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/MultipleFieldDeclsInStatement.cpp @@ -0,0 +1,11 @@ +// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s + +namespace bar { + +// The order of fields should not change. +struct Foo { + int x, y; // CHECK: {{^ int x, y;}} + double z; // CHECK-NEXT: {{^ double z;}} +}; + +} // end namespace bar diff --git a/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveAroundDefinition.cpp b/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveAroundDefinition.cpp new file mode 100644 index 000000000000..f00b4b0b57bf --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveAroundDefinition.cpp @@ -0,0 +1,15 @@ +// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s + +namespace bar { + +#define DEFINE_FOO + +// This is okay to reorder. +#ifdef DEFINE_FOO +struct Foo { + int x; // CHECK: {{^ int y;}} + int y; // CHECK-NEXT: {{^ int x;}} +}; +#endif + +} // end namespace bar diff --git a/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveAroundFields.cpp b/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveAroundFields.cpp new file mode 100644 index 000000000000..c37546a05afd --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveAroundFields.cpp @@ -0,0 +1,15 @@ +// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s + +namespace bar { + +#define DEFINE_FIELDS + +// This is okay to reorder. +struct Foo { +#ifdef DEFINE_FIELDS // CHECK: {{^#ifdef DEFINE_FIELDS}} + int x; // CHECK-NEXT: {{^ int y;}} + int y; // CHECK-NEXT: {{^ int x;}} +#endif // CHECK-NEXT: {{^#endif}} +}; + +} // end namespace bar diff --git a/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveInDefinition.cpp b/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveInDefinition.cpp new file mode 100644 index 000000000000..fee6b0e637b9 --- /dev/null +++ b/clang-tools-extra/test/clang-reorder-fields/PreprocessorDirectiveInDefinition.cpp @@ -0,0 +1,16 @@ +// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s + +namespace bar { + +#define ADD_Z + +// The order of fields should not change. +struct Foo { + int x; // CHECK: {{^ int x;}} + int y; // CHECK-NEXT: {{^ int y;}} +#ifdef ADD_Z // CHECK-NEXT: {{^#ifdef ADD_Z}} + int z; // CHECK-NEXT: {{^ int z;}} +#endif // CHECK-NEXT: {{^#endif}} +}; + +} // end namespace bar diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/multi-level-implicit-pointer-conversion.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone/multi-level-implicit-pointer-conversion.c new file mode 100644 index 000000000000..9dc5dae5c80f --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/multi-level-implicit-pointer-conversion.c @@ -0,0 +1,11 @@ +// RUN: %check_clang_tidy -check-suffixes=ENABLE-IN-C %s bugprone-multi-level-implicit-pointer-conversion %t -- -config="{CheckOptions: {bugprone-multi-level-implicit-pointer-conversion.EnableInC: true}}" +// RUN: %check_clang_tidy -check-suffixes=DISABLE-IN-C %s bugprone-multi-level-implicit-pointer-conversion %t -- -config="{CheckOptions: {bugprone-multi-level-implicit-pointer-conversion.EnableInC: false}}" + +void free(void*); + +void test() { + char **p; + free(p); + // CHECK-MESSAGES-ENABLE-IN-C: :[[@LINE-1]]:8: warning: multilevel pointer conversion from 'char **' to 'void *', please use explicit cast [bugprone-multi-level-implicit-pointer-conversion] + free((void *)p); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/use-enum-class.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/use-enum-class.cpp new file mode 100644 index 000000000000..f53d787f80ef --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/use-enum-class.cpp @@ -0,0 +1,62 @@ +// RUN: %check_clang_tidy -std=c++11-or-later -check-suffix=ALL,DEFAULT %s \ +// RUN: cppcoreguidelines-use-enum-class %t -- + +// RUN: %check_clang_tidy -std=c++11-or-later -check-suffix=ALL %s \ +// RUN: cppcoreguidelines-use-enum-class %t -- \ +// RUN: -config="{CheckOptions: { \ +// RUN: cppcoreguidelines-use-enum-class.IgnoreUnscopedEnumsInClasses: true \ +// RUN: }}" -- + +enum E {}; +// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'E' is unscoped, use 'enum class' instead + +enum class EC {}; + +enum struct ES {}; + +struct S { + enum E {}; + // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead + enum class EC {}; +}; + +class C { + enum E {}; + // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead + enum class EC {}; +}; + +template +class TC { + enum E {}; + // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead + enum class EC {}; +}; + +union U { + enum E {}; + // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead + enum class EC {}; +}; + +namespace { +enum E {}; +// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'E' is unscoped, use 'enum class' instead +enum class EC {}; +} // namespace + +namespace N { +enum E {}; +// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'E' is unscoped, use 'enum class' instead +enum class EC {}; +} // namespace N + +template +static void foo(); + +enum ForwardE : int; +// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'ForwardE' is unscoped, use 'enum class' instead + +enum class ForwardEC : int; + +enum struct ForwardES : int; diff --git a/clang-tools-extra/test/clang-tidy/checkers/google/readability-namespace-comments-missing-nested-namespaces.cpp b/clang-tools-extra/test/clang-tidy/checkers/google/readability-namespace-comments-missing-nested-namespaces.cpp new file mode 100644 index 000000000000..de42df30cb79 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/google/readability-namespace-comments-missing-nested-namespaces.cpp @@ -0,0 +1,32 @@ +// RUN: %check_clang_tidy %s google-readability-namespace-comments %t -std=c++20 \ +// RUN: '-config={CheckOptions: { \ +// RUN: google-readability-namespace-comments.AllowOmittingNamespaceComments: true, \ +// RUN: google-readability-namespace-comments.ShortNamespaceLines: 0, \ +// RUN: }}' + +// accept if namespace comments are fully omitted +namespace n1::n2 { +namespace /*comment1*/n3/*comment2*/::/*comment3*/inline/*comment4*/n4/*comment5*/ { +void f(); +}} + +namespace n5::inline n6 { +void f(); +} + +namespace n7::inline n8 { +void f(); +} + +// accept if namespace comments are partly omitted (e.g. only for nested namespace) +namespace n1::n2 { +namespace n3::n4 { +void f(); +} +} // namespace n1::n2 + +// fail if namespace comment is different than expected +namespace n9::inline n10 { +void f(); +} // namespace n9::n10 +// CHECK-MESSAGES: :[[@LINE-1]]:2: warning: namespace 'n9::inline n10' ends with a comment that refers to a wrong namespace 'n9::n10' [google-readability-namespace-comments] diff --git a/clang-tools-extra/test/clang-tidy/checkers/google/readability-namespace-comments-missing.cpp b/clang-tools-extra/test/clang-tidy/checkers/google/readability-namespace-comments-missing.cpp new file mode 100644 index 000000000000..019add1ed06d --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/google/readability-namespace-comments-missing.cpp @@ -0,0 +1,40 @@ +// RUN: %check_clang_tidy %s google-readability-namespace-comments %t \ +// RUN: -config='{CheckOptions: { \ +// RUN: google-readability-namespace-comments.AllowOmittingNamespaceComments: true, \ +// RUN: google-readability-namespace-comments.ShortNamespaceLines: 0, \ +// RUN: }}' + +// accept if namespace comments are fully omitted +namespace n1 { +namespace /* a comment */ n2 /* another comment */ { +void f(); +}} + +#define MACRO macro_expansion +namespace MACRO { +void f(); +} + +namespace [[deprecated("foo")]] namespace_with_attr { +inline namespace inline_namespace { +void f(); +} +} + +namespace [[]] { +void f(); +} + +// accept if namespace comments are partly omitted (e.g. only for nested namespace) +namespace n3 { +namespace n4 { +void f(); +} // n4 +} + +// fail if namespace comment is different than expected +namespace n1 { +void f(); +} // namespace n2 +// CHECK-MESSAGES: :[[@LINE-1]]:2: warning: namespace 'n1' ends with a comment that refers to a wrong namespace 'n2' [google-readability-namespace-comments] + diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type-transform-lambdas-cxx14.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type-transform-lambdas-cxx14.cpp new file mode 100644 index 000000000000..33051c63e4f1 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type-transform-lambdas-cxx14.cpp @@ -0,0 +1,24 @@ +// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-use-trailing-return-type %t -- -- -fno-delayed-template-parsing + +namespace std { + template + class vector {}; + + class string {}; +} // namespace std + +void test_lambda_positive() { + auto l1 = [](auto x) { return x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type] + // CHECK-FIXES: {{^}} auto l1 = [](auto x) -> auto { return x; };{{$}} +} + +template