diff --git a/.ci/compute_projects.py b/.ci/compute_projects.py index 40dd0507a9eaf..c3cf714ce6c10 100644 --- a/.ci/compute_projects.py +++ b/.ci/compute_projects.py @@ -49,12 +49,22 @@ }, "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", + }, } # This mapping describes runtimes that should be enabled for a specific project, @@ -64,7 +74,17 @@ # This mapping describes runtimes that should be tested when the key project is # touched. -DEPENDENT_RUNTIMES_TO_TEST = {"clang": {"libcxx", "libcxxabi", "libunwind"}} +DEPENDENT_RUNTIMES_TO_TEST = { + "clang": {"compiler-rt"}, + "clang-tools-extra": {"libc"}, + "libc": {"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. @@ -93,9 +113,6 @@ "cross-project-tests", "flang", "libc", - "libcxx", - "libcxxabi", - "libunwind", "lldb", "openmp", "polly", @@ -122,21 +139,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: @@ -154,54 +185,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 in DEPENDENT_RUNTIMES_TO_TEST: - runtimes_to_test.update(DEPENDENT_RUNTIMES_TO_TEST[project_to_test]) - if project_to_test in DEPENDENT_RUNTIMES_TO_BUILD: - runtimes_to_test.update(DEPENDENT_RUNTIMES_TO_BUILD[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(projects_to_test: Set[str]) -> Set[str]: - check_targets = set() - for project_to_test in projects_to_test: - if project_to_test not in DEPENDENT_RUNTIMES_TO_TEST: - continue - for runtime_to_test in DEPENDENT_RUNTIMES_TO_TEST[project_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]: @@ -225,10 +254,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_build = _compute_runtimes_to_test(projects_to_test) - runtimes_check_targets = _compute_runtime_check_targets(projects_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 @@ -238,6 +276,9 @@ def get_env_variables(modified_files: list[str], platform: str) -> Set[str]: "project_check_targets": " ".join(sorted(projects_check_targets)), "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 ae376ea6a43cd..6299931e1ec34 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,8 +185,9 @@ 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): + def test_exclude_libcxx_in_projects(self): env_variables = compute_projects.get_env_variables( ["libcxx/CMakeLists.txt"], "Linux" ) @@ -169,6 +195,17 @@ 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_include_libc_in_runtimes(self): + env_variables = compute_projects.get_env_variables( + ["libc/CMakeLists.txt"], "Linux" + ) + self.assertEqual(env_variables["projects_to_build"], "clang;lld") + self.assertEqual(env_variables["project_check_targets"], "") + 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"], "") def test_exclude_docs(self): env_variables = compute_projects.get_env_variables( @@ -178,6 +215,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,21 +225,30 @@ 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", ) @@ -215,6 +262,19 @@ def test_lldb(self): 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__": diff --git a/.ci/generate-buildkite-pipeline-premerge b/.ci/generate-buildkite-pipeline-premerge deleted file mode 100755 index 5e5f916f35b72..0000000000000 --- 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 <&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 @@ -57,10 +52,11 @@ 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" -echo "--- cmake" +echo "::group::cmake" export PIP_BREAK_SYSTEM_PACKAGES=1 pip install -q -r "${MONOREPO_ROOT}"/.ci/all_requirements.txt @@ -89,32 +85,49 @@ cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \ -D LLDB_ENFORCE_STRICT_TEST_REQUIREMENTS=ON \ -D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" -echo "--- ninja" +echo "::endgroup::" +echo "::group::ninja" + # Targets are not escaped as they are passed as separate arguments. ninja -C "${BUILD_DIR}" -k 0 ${targets} +echo "::endgroup::" + +if [[ "${runtime_targets}" != "" ]]; then + echo "::group::ninja runtimes" + + ninja -C "${BUILD_DIR}" ${runtime_targets} + + echo "::endgroup::" +fi + # Compiling runtimes with just-built Clang and running their tests # as an additional testing for Clang. -if [[ "${runtimes_targets}" != "" ]]; then - echo "--- cmake runtimes C++26" +if [[ "${runtime_targets_needs_reconfig}" != "" ]]; then + echo "::group::cmake runtimes C++26" cmake \ -D LIBCXX_TEST_PARAMS="std=c++26" \ -D LIBCXXABI_TEST_PARAMS="std=c++26" \ "${BUILD_DIR}" - echo "--- ninja runtimes C++26" + echo "::endgroup::" + echo "::group::ninja runtimes C++26" - ninja -C "${BUILD_DIR}" ${runtime_targets} + ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} - echo "--- cmake runtimes clang modules" + echo "::endgroup::" + echo "::group::cmake runtimes clang modules" cmake \ -D LIBCXX_TEST_PARAMS="enable_modules=clang" \ -D LIBCXXABI_TEST_PARAMS="enable_modules=clang" \ "${BUILD_DIR}" - echo "--- ninja runtimes clang modules" + echo "::endgroup::" + echo "::group::ninja runtimes clang modules" - ninja -C "${BUILD_DIR}" ${runtime_targets} + ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig} + + echo "::endgroup::" fi diff --git a/.ci/monolithic-windows.sh b/.ci/monolithic-windows.sh index a0997420b0d3f..c27111bf5aa10 100755 --- a/.ci/monolithic-windows.sh +++ b/.ci/monolithic-windows.sh @@ -37,21 +37,16 @@ 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 projects="${1}" targets="${2}" -echo "--- cmake" +echo "::group::cmake" pip install -q -r "${MONOREPO_ROOT}"/.ci/all_requirements.txt export CC=cl @@ -83,6 +78,10 @@ cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \ -D LLVM_PARALLEL_COMPILE_JOBS=${MAX_PARALLEL_COMPILE_JOBS} \ -D LLVM_PARALLEL_LINK_JOBS=${MAX_PARALLEL_LINK_JOBS} -echo "--- ninja" +echo "::endgroup::" +echo "::group::ninja" + # Targets are not escaped as they are passed as separate arguments. ninja -C "${BUILD_DIR}" -k 0 ${targets} + +echo "::endgroup" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000000..03748938700e3 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,4 @@ +When performing a code review, pay close attention to code modifying a function's +control flow. Could the change result in the corruption of performance profile +data? Could the change result in invalid debug information, in particular for +branches and calls? diff --git a/.github/new-prs-labeler.yml b/.github/new-prs-labeler.yml index 162161ff13fb0..863090af9af7e 100644 --- a/.github/new-prs-labeler.yml +++ b/.github/new-prs-labeler.yml @@ -632,6 +632,10 @@ llvm:instcombine: - llvm/test/Transforms/InstCombine/** - llvm/test/Transforms/InstSimplify/** +llvm:vectorcombine: + - llvm/lib/Transforms/Vectorize/VectorCombine.cpp + - llvm/test/Transforms/VectorCombine/** + clangd: - clang-tools-extra/clangd/** @@ -777,6 +781,10 @@ backend:NVPTX: - 'llvm/**/*nvptx*/**' - 'llvm/**/*NVPTX*/**' +backend:MIPS: + - '**/*mips*' + - '**/*Mips*' + backend:RISC-V: - clang/**/*riscv* - clang/**/*RISCV* diff --git a/.github/workflows/hlsl-test-all.yaml b/.github/workflows/hlsl-test-all.yaml index 93a1c6d2662d4..b6530fe11b840 100644 --- a/.github/workflows/hlsl-test-all.yaml +++ b/.github/workflows/hlsl-test-all.yaml @@ -43,13 +43,13 @@ jobs: - name: Checkout OffloadTest uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: - repository: llvm-beanz/offload-test-suite + repository: llvm/offload-test-suite ref: main path: OffloadTest - name: Checkout Golden Images uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: - repository: llvm-beanz/offload-golden-images + repository: llvm/offload-golden-images ref: main path: golden-images - name: Setup Windows diff --git a/.github/workflows/libcxx-restart-preempted-jobs.yaml b/.github/workflows/libcxx-restart-preempted-jobs.yaml index 7b341d7f22e41..accb84efb5c90 100644 --- a/.github/workflows/libcxx-restart-preempted-jobs.yaml +++ b/.github/workflows/libcxx-restart-preempted-jobs.yaml @@ -20,7 +20,7 @@ permissions: jobs: restart: - if: github.repository_owner == 'llvm' && (github.event.workflow_run.conclusion == 'failure' || github.event.workflow_run.conclusion == 'cancelled') + if: github.repository_owner == 'llvm' && (github.event.workflow_run.conclusion == 'failure') name: "Restart Job" permissions: statuses: read @@ -32,8 +32,11 @@ jobs: uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1 with: script: | - const failure_regex = /Process completed with exit code 1./ - const preemption_regex = /The runner has received a shutdown signal/ + // The "The run was canceled by" message comes from a user manually canceling a workflow + // the "higher priority" message comes from github canceling a workflow because the user updated the change. + // And the "exit code 1" message indicates a genuine failure. + const failure_regex = /(Process completed with exit code 1.)/ + 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}`) @@ -74,7 +77,7 @@ jobs: console.log('Check run was not completed. Skipping.'); continue; } - if (check_run.conclusion != 'failure' && check_run.conclusion != 'cancelled') { + if (check_run.conclusion != 'failure') { console.log('Check run had conclusion: ' + check_run.conclusion + '. Skipping.'); continue; } @@ -153,91 +156,3 @@ jobs: run_id: context.payload.workflow_run.id }) await create_check_run('success', 'Restarted workflow run due to preempted job') - - restart-test: - if: github.repository_owner == 'llvm' && (github.event.workflow_run.conclusion == 'failure' || github.event.workflow_run.conclusion == 'cancelled') && github.event.actor.login == 'ldionne' # TESTING ONLY - name: "Restart Job (test)" - permissions: - statuses: read - checks: write - actions: write - runs-on: ubuntu-24.04 - steps: - - name: "Restart Job (test)" - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1 - with: - script: | - const FAILURE_REGEX = /Process completed with exit code 1./ - const PREEMPTION_REGEX = /(The runner has received a shutdown signal)|(The operation was canceled)/ - - function log(msg) { - core.notice(msg) - } - - const wf_run = context.payload.workflow_run - log(`Running on "${wf_run.display_title}" by @${wf_run.actor.login} (event: ${wf_run.event})\nWorkflow run URL: ${wf_run.html_url}`) - - log('Listing check runs for suite') - const check_suites = await github.rest.checks.listForSuite({ - owner: context.repo.owner, - repo: context.repo.repo, - check_suite_id: context.payload.workflow_run.check_suite_id, - per_page: 100 // FIXME: We don't have 100 check runs yet, but we should handle this better. - }) - - preemptions = []; - legitimate_failures = []; - for (check_run of check_suites.data.check_runs) { - log(`Checking check run: ${check_run.id}`); - if (check_run.status != 'completed') { - log('Check run was not completed. Skipping.'); - continue; - } - - if (check_run.conclusion != 'failure' && check_run.conclusion != 'cancelled') { - log(`Check run had conclusion: ${check_run.conclusion}. Skipping.`); - continue; - } - - annotations = await github.rest.checks.listAnnotations({ - owner: context.repo.owner, - repo: context.repo.repo, - check_run_id: check_run.id - }) - - preemption_annotation = annotations.data.find(function(annotation) { - return annotation.annotation_level == 'failure' && - annotation.message.match(PREEMPTION_REGEX) != null; - }); - if (preemption_annotation != null) { - log(`Found preemption message: ${preemption_annotation.message}`); - preemptions.push(check_run); - break; - } - - failure_annotation = annotations.data.find(function(annotation) { - return annotation.annotation_level == 'failure' && - annotation.message.match(FAILURE_REGEX) != null; - }); - if (failure_annotation != null) { - log(`Found legitimate failure annotation: ${failure_annotation.message}`); - legitimate_failures.push(check_run); - break; - } - } - - if (preemptions) { - log('Found some preempted jobs'); - if (legitimate_failures) { - log('Also found some legitimate failures, so not restarting the workflow.'); - } else { - log('Did not find any legitimate failures. Restarting workflow.'); - await github.rest.actions.reRunWorkflowFailedJobs({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id - }) - } - } else { - log('Did not find any preempted jobs. Not restarting the workflow.'); - } diff --git a/.github/workflows/libsycl-build-and-test.yaml b/.github/workflows/libsycl-build-and-test.yaml new file mode 100644 index 0000000000000..dab9c3996ddee --- /dev/null +++ b/.github/workflows/libsycl-build-and-test.yaml @@ -0,0 +1,41 @@ +# This file defines pre-commit CI for libsycl++. +name: Build libsycl +on: + pull_request: + paths: + - 'libsycl/**' + - '.github/workflows/libsycl-build-and-test.yaml' + +permissions: + contents: read # Default everything to read-only + +concurrency: + # Cancel a currently running workflow from the same PR + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + build_ubuntu2204: + # sergey-semenov repo is set is for test purposes + if: github.repository_owner == 'sergey-semenov' + # github runner + runs-on: ubuntu-22.04 + # reuse libcxx container for now + container: ghcr.io/llvm/libcxx-linux-builder:2b57ebb50b6d418e70382e655feaa619b558e254 + continue-on-error: false + steps: + - uses: actions/checkout@v4 + - name: Register cleanup after job is finished + uses: ./libsycl/utils/ci/actions/cleanup + - name: Compile + env: + CC: 'clang-21' + CXX: 'clang++-21' + run: | + mkdir -p $GITHUB_WORKSPACE/build + mkdir -p $GITHUB_WORKSPACE/install + cmake -G Ninja -S $GITHUB_WORKSPACE/runtimes -B $GITHUB_WORKSPACE/build \ + -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install \ + -DLLVM_ENABLE_RUNTIMES="libsycl" \ + -DCMAKE_BUILD_TYPE=Release + ninja -C $GITHUB_WORKSPACE/build install --verbose diff --git a/.github/workflows/premerge.yaml b/.github/workflows/premerge.yaml index 709b6d03d94c3..4435a3e905768 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/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h index 64e524e2f13d7..91ecf89da618c 100644 --- a/bolt/include/bolt/Core/BinaryContext.h +++ b/bolt/include/bolt/Core/BinaryContext.h @@ -73,14 +73,15 @@ struct SegmentInfo { uint64_t FileSize; /// Size in file. uint64_t Alignment; /// Alignment of the segment. bool IsExecutable; /// Is the executable bit set on the Segment? + bool IsWritable; /// Is the segment writable. void print(raw_ostream &OS) const { OS << "SegmentInfo { Address: 0x" << Twine::utohexstr(Address) << ", Size: 0x" << Twine::utohexstr(Size) << ", FileOffset: 0x" << Twine::utohexstr(FileOffset) << ", FileSize: 0x" << Twine::utohexstr(FileSize) << ", Alignment: 0x" - << Twine::utohexstr(Alignment) << ", " << (IsExecutable ? "x" : " ") - << "}"; + << Twine::utohexstr(Alignment) << ", " << (IsExecutable ? "x" : "") + << (IsWritable ? "w" : "") << " }"; }; }; @@ -333,9 +334,14 @@ class BinaryContext { std::optional Source, unsigned CUID, unsigned DWARFVersion); + /// Input file segment info + /// /// [start memory address] -> [segment info] mapping. std::map SegmentMapInfo; + /// Newly created segments. + std::vector NewSegments; + /// Symbols that are expected to be undefined in MCContext during emission. std::unordered_set UndefinedSymbols; diff --git a/bolt/include/bolt/Core/BinarySection.h b/bolt/include/bolt/Core/BinarySection.h index ad2fed2cf27eb..154a8d12de5ce 100644 --- a/bolt/include/bolt/Core/BinarySection.h +++ b/bolt/include/bolt/Core/BinarySection.h @@ -523,11 +523,6 @@ inline uint8_t *copyByteArray(const uint8_t *Data, uint64_t Size) { return Array; } -inline uint8_t *copyByteArray(StringRef Buffer) { - return copyByteArray(reinterpret_cast(Buffer.data()), - Buffer.size()); -} - inline uint8_t *copyByteArray(ArrayRef Buffer) { return copyByteArray(reinterpret_cast(Buffer.data()), Buffer.size()); diff --git a/bolt/include/bolt/Core/DIEBuilder.h b/bolt/include/bolt/Core/DIEBuilder.h index 32e455ad3030a..e4a4fc6b2f258 100644 --- a/bolt/include/bolt/Core/DIEBuilder.h +++ b/bolt/include/bolt/Core/DIEBuilder.h @@ -20,8 +20,8 @@ #include "llvm/CodeGen/DIE.h" #include "llvm/DebugInfo/DWARF/DWARFAbbreviationDeclaration.h" #include "llvm/DebugInfo/DWARF/DWARFDie.h" -#include "llvm/DebugInfo/DWARF/DWARFExpression.h" #include "llvm/DebugInfo/DWARF/DWARFUnit.h" +#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h" #include "llvm/Support/Allocator.h" #include diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h index 804100db80793..b99cf8b40ef54 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -1393,7 +1393,7 @@ class MCPlusBuilder { return getTargetSymbol(BinaryExpr->getLHS()); auto *SymbolRefExpr = dyn_cast(Expr); - if (SymbolRefExpr && SymbolRefExpr->getKind() == MCSymbolRefExpr::VK_None) + if (SymbolRefExpr && SymbolRefExpr->getSpecifier() == 0) return &SymbolRefExpr->getSymbol(); return nullptr; diff --git a/bolt/include/bolt/Passes/PAuthGadgetScanner.h b/bolt/include/bolt/Passes/PAuthGadgetScanner.h index c6b9cc2eb4b9c..721fd664a3253 100644 --- a/bolt/include/bolt/Passes/PAuthGadgetScanner.h +++ b/bolt/include/bolt/Passes/PAuthGadgetScanner.h @@ -199,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 @@ -273,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; @@ -282,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, @@ -322,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 3f07a6dc03a4f..98e4bba872846 100644 --- a/bolt/include/bolt/Profile/DataAggregator.h +++ b/bolt/include/bolt/Profile/DataAggregator.h @@ -85,6 +85,8 @@ class DataAggregator : public DataReader { }; friend raw_ostream &operator<<(raw_ostream &OS, const LBREntry &); + friend struct PerfSpeEventsTestHelper; + struct PerfBranchSample { SmallVector LBR; }; @@ -99,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 { @@ -126,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; @@ -200,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. /// @@ -261,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 @@ -362,6 +374,9 @@ class DataAggregator : public DataReader { /// Parse a single pair of binary full path and associated build-id std::optional> parseNameBuildIDPair(); + /// Coordinate reading and parsing of perf.data file + void parsePerfData(BinaryContext &BC); + /// Coordinate reading and parsing of pre-aggregated file /// /// The regular perf2bolt aggregation job is to read perf output directly. @@ -379,9 +394,9 @@ class DataAggregator : public DataReader { /// File format syntax: /// E /// S - /// T + /// [TR] /// B - /// [Ff] + /// [Ffr] /// /// where , , have the format [:] /// @@ -392,8 +407,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 @@ -516,6 +534,26 @@ inline raw_ostream &operator<<(raw_ostream &OS, 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/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h index 94dd06e91d6b1..91d62a78de390 100644 --- a/bolt/include/bolt/Rewrite/RewriteInstance.h +++ b/bolt/include/bolt/Rewrite/RewriteInstance.h @@ -202,6 +202,9 @@ class RewriteInstance { /// Map code sections generated by BOLT. void mapCodeSections(BOLTLinker::SectionMapper MapSection); + /// Map code without relocating sections. + void mapCodeSectionsInPlace(BOLTLinker::SectionMapper MapSection); + /// Map the rest of allocatable sections. void mapAllocatableSections(BOLTLinker::SectionMapper MapSection); @@ -297,6 +300,9 @@ class RewriteInstance { return FUNC(ELF64BE); \ } + /// Update loadable segment information based on new sections. + void updateSegmentInfo(); + /// Patch ELF book-keeping info. void patchELFPHDRTable(); diff --git a/bolt/include/bolt/Utils/CommandLineOpts.h b/bolt/include/bolt/Utils/CommandLineOpts.h index 4acce5a3e8320..a75b6bf720ec4 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 b998d7160aae7..eec68ff5a5fce 100644 --- a/bolt/lib/Core/BinaryFunction.cpp +++ b/bolt/lib/Core/BinaryFunction.cpp @@ -3328,10 +3328,7 @@ void BinaryFunction::duplicateConstantIslands() { // Update instruction reference Operand = MCOperand::createExpr(BC.MIB->getTargetExprFor( - Inst, - MCSymbolRefExpr::create(ColdSymbol, MCSymbolRefExpr::VK_None, - *BC.Ctx), - *BC.Ctx, 0)); + Inst, MCSymbolRefExpr::create(ColdSymbol, *BC.Ctx), *BC.Ctx, 0)); ++OpNum; } } diff --git a/bolt/lib/Core/CMakeLists.txt b/bolt/lib/Core/CMakeLists.txt index 8c1f5d0bb37b5..fc72dc023c590 100644 --- a/bolt/lib/Core/CMakeLists.txt +++ b/bolt/lib/Core/CMakeLists.txt @@ -1,5 +1,6 @@ set(LLVM_LINK_COMPONENTS DebugInfoDWARF + DebugInfoDWARFLowLevel Demangle MC MCDisassembler diff --git a/bolt/lib/Core/DIEBuilder.cpp b/bolt/lib/Core/DIEBuilder.cpp index d36dbb3459249..b041dc5ea1cce 100644 --- a/bolt/lib/Core/DIEBuilder.cpp +++ b/bolt/lib/Core/DIEBuilder.cpp @@ -14,11 +14,11 @@ #include "llvm/CodeGen/DIE.h" #include "llvm/DebugInfo/DWARF/DWARFAbbreviationDeclaration.h" #include "llvm/DebugInfo/DWARF/DWARFDie.h" -#include "llvm/DebugInfo/DWARF/DWARFExpression.h" #include "llvm/DebugInfo/DWARF/DWARFFormValue.h" #include "llvm/DebugInfo/DWARF/DWARFTypeUnit.h" #include "llvm/DebugInfo/DWARF/DWARFUnit.h" #include "llvm/DebugInfo/DWARF/DWARFUnitIndex.h" +#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" diff --git a/bolt/lib/Core/DebugNames.cpp b/bolt/lib/Core/DebugNames.cpp index aa1c8f3d42d4b..a9d98a6ba879b 100644 --- a/bolt/lib/Core/DebugNames.cpp +++ b/bolt/lib/Core/DebugNames.cpp @@ -8,8 +8,8 @@ #include "bolt/Core/DebugNames.h" #include "bolt/Core/BinaryContext.h" -#include "llvm/DebugInfo/DWARF/DWARFExpression.h" #include "llvm/DebugInfo/DWARF/DWARFTypeUnit.h" +#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h" #include "llvm/Support/EndianStream.h" #include "llvm/Support/LEB128.h" #include diff --git a/bolt/lib/Passes/BinaryPasses.cpp b/bolt/lib/Passes/BinaryPasses.cpp index e356481bbdc7c..5d44e1a1a4902 100644 --- a/bolt/lib/Passes/BinaryPasses.cpp +++ b/bolt/lib/Passes/BinaryPasses.cpp @@ -1765,27 +1765,26 @@ Error PrintProgramStats::runOnFunctions(BinaryContext &BC) { if (opts::ShowDensity) { double Density = 0.0; - // Sorted by the density in descending order. - llvm::stable_sort(FuncDensityList, - [&](const std::pair &A, - const std::pair &B) { - if (A.first != B.first) - return A.first > B.first; - return A.second < B.second; - }); + llvm::sort(FuncDensityList); uint64_t AccumulatedSamples = 0; - uint32_t I = 0; assert(opts::ProfileDensityCutOffHot <= 1000000 && "The cutoff value is greater than 1000000(100%)"); - while (AccumulatedSamples < - TotalSampleCount * - static_cast(opts::ProfileDensityCutOffHot) / - 1000000 && - I < FuncDensityList.size()) { - AccumulatedSamples += FuncDensityList[I].second; - Density = FuncDensityList[I].first; - I++; + // Subtract samples in zero-density functions (no fall-throughs) from + // TotalSampleCount (not used anywhere below). + for (const auto [CurDensity, CurSamples] : FuncDensityList) { + if (CurDensity != 0.0) + break; + TotalSampleCount -= CurSamples; + } + const uint64_t CutoffSampleCount = + 1.f * TotalSampleCount * opts::ProfileDensityCutOffHot / 1000000; + // Process functions in decreasing density order + for (const auto [CurDensity, CurSamples] : llvm::reverse(FuncDensityList)) { + if (AccumulatedSamples >= CutoffSampleCount) + break; + AccumulatedSamples += CurSamples; + Density = CurDensity; } if (Density == 0.0) { BC.errs() << "BOLT-WARNING: the output profile is empty or the " diff --git a/bolt/lib/Passes/Instrumentation.cpp b/bolt/lib/Passes/Instrumentation.cpp index fbf889279f1c0..c2f876f0dff9e 100644 --- a/bolt/lib/Passes/Instrumentation.cpp +++ b/bolt/lib/Passes/Instrumentation.cpp @@ -666,8 +666,7 @@ Error Instrumentation::runOnFunctions(BinaryContext &BC) { auto IsLEA = [&BC](const MCInst &Inst) { return BC.MIB->isLEA64r(Inst); }; const auto LEA = std::find_if( std::next(llvm::find_if(reverse(BB), IsLEA)), BB.rend(), IsLEA); - LEA->getOperand(4).setExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *BC.Ctx)); + LEA->getOperand(4).setExpr(MCSymbolRefExpr::create(Target, *BC.Ctx)); } else { BC.errs() << "BOLT-WARNING: ___GLOBAL_init_65535 not found\n"; } diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp index 971ea5fdef420..f928dd49edb25 100644 --- a/bolt/lib/Passes/PAuthGadgetScanner.cpp +++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp @@ -82,6 +82,22 @@ namespace PAuthGadgetScanner { dbgs() << "\n"; } +// Iterates over BinaryFunction's instructions like a range-based for loop: +// +// iterateOverInstrs(BF, [&](MCInstReference Inst) { +// // loop body +// }); +template static void iterateOverInstrs(BinaryFunction &BF, T Fn) { + if (BF.hasCFG()) { + for (BinaryBasicBlock &BB : BF) + for (int64_t I = 0, E = BB.size(); I < E; ++I) + Fn(MCInstInBBReference(&BB, I)); + } else { + for (auto I : BF.instrs()) + Fn(MCInstInBFReference(&BF, I.first)); + } +} + // This class represents mapping from a set of arbitrary physical registers to // consecutive array indexes. class TrackedRegisters { @@ -152,6 +168,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 +213,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 +248,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 +260,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 +296,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 +340,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]; } @@ -342,6 +358,29 @@ class SrcSafetyAnalysis { return S; } + /// Computes a reasonably pessimistic estimation of the register state when + /// the previous instruction is not known for sure. Takes the set of registers + /// which are trusted at function entry and removes all registers that can be + /// clobbered inside this function. + SrcState computePessimisticState(BinaryFunction &BF) { + BitVector ClobberedRegs(NumRegs); + iterateOverInstrs(BF, [&](MCInstReference Inst) { + BC.MIB->getClobberedRegs(Inst, ClobberedRegs); + + // If this is a call instruction, no register is safe anymore, unless + // it is a tail call. Ignore tail calls for the purpose of estimating the + // worst-case scenario, assuming no instructions are executed in the + // caller after this point anyway. + if (BC.MIB->isCall(Inst) && !BC.MIB->isTailCall(Inst)) + ClobberedRegs.set(); + }); + + SrcState S = createEntryState(); + S.SafeToDerefRegs.reset(ClobberedRegs); + S.TrustedRegs.reset(ClobberedRegs); + return S; + } + BitVector getClobberedRegs(const MCInst &Point) const { BitVector Clobbered(NumRegs); // Assume a call can clobber all registers, including callee-saved @@ -430,11 +469,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"; @@ -543,6 +584,10 @@ class DataflowSrcSafetyAnalysis using SrcSafetyAnalysis::BC; using SrcSafetyAnalysis::computeNext; + // Pessimistic initial state for basic blocks without any predecessors + // (not needed for most functions, thus initialized lazily). + SrcState PessimisticState; + public: DataflowSrcSafetyAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId, @@ -583,6 +628,18 @@ class DataflowSrcSafetyAnalysis if (BB.isEntryPoint()) return createEntryState(); + // If a basic block without any predecessors is found in an optimized code, + // this likely means that some CFG edges were not detected. Pessimistically + // assume any register that can ever be clobbered in this function to be + // unsafe before this basic block. + // Warn about this fact in FunctionAnalysis::findUnsafeUses(), as it likely + // means imprecise CFG information. + if (BB.pred_empty()) { + if (PessimisticState.empty()) + PessimisticState = computePessimisticState(*BB.getParent()); + return PessimisticState; + } + return SrcState(); } @@ -612,6 +669,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. // @@ -644,37 +737,29 @@ class DataflowSrcSafetyAnalysis // // Then, a function can be split into a number of disjoint contiguous sequences // 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 { +// the same way basic blocks are processed by data-flow analysis, with the same +// pessimistic estimation of the initial state at the start of each sequence +// (except the first instruction of the function). +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). - SrcState createUnsafeState() const { - return SrcState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters()); - } public: 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 { + const SrcState DefaultState = computePessimisticState(BF); 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, @@ -684,15 +769,11 @@ class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis { LLVM_DEBUG({ traceInst(BC, "Due to label, resetting the state before", Inst); }); - S = createUnsafeState(); + S = DefaultState; } - // 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 +781,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 +796,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, @@ -763,6 +1319,90 @@ shouldReportReturnGadget(const BinaryContext &BC, const MCInstReference &Inst, return make_gadget_report(RetKind, Inst, *RetReg); } +/// While BOLT already marks some of the branch instructions as tail calls, +/// this function tries to detect less obvious cases, assuming false positives +/// are acceptable as long as there are not too many of them. +/// +/// It is possible that not all the instructions classified as tail calls by +/// this function are safe to be considered as such for the purpose of code +/// transformations performed by BOLT. The intention of this function is to +/// spot some of actually missed tail calls (and likely a number of unrelated +/// indirect branch instructions) as long as this doesn't increase the amount +/// of false positive reports unacceptably. +static bool shouldAnalyzeTailCallInst(const BinaryContext &BC, + const BinaryFunction &BF, + const MCInstReference &Inst) { + // Some BC.MIB->isXYZ(Inst) methods simply delegate to MCInstrDesc::isXYZ() + // (such as isBranch at the time of writing this comment), some don't (such + // as isCall). For that reason, call MCInstrDesc's methods explicitly when + // it is important. + const MCInstrDesc &Desc = + BC.MII->get(static_cast(Inst).getOpcode()); + // Tail call should be a branch (but not necessarily an indirect one). + if (!Desc.isBranch()) + return false; + + // Always analyze the branches already marked as tail calls by BOLT. + if (BC.MIB->isTailCall(Inst)) + return true; + + // Try to also check the branches marked as "UNKNOWN CONTROL FLOW" - the + // below is a simplified condition from BinaryContext::printInstruction. + bool IsUnknownControlFlow = + BC.MIB->isIndirectBranch(Inst) && !BC.MIB->getJumpTable(Inst); + + if (BF.hasCFG() && IsUnknownControlFlow) + return true; + + return false; +} + +static std::optional> +shouldReportUnsafeTailCall(const BinaryContext &BC, const BinaryFunction &BF, + const MCInstReference &Inst, const SrcState &S) { + static const GadgetKind UntrustedLRKind( + "untrusted link register found before tail call"); + + if (!shouldAnalyzeTailCallInst(BC, BF, Inst)) + return std::nullopt; + + // Not only the set of registers returned by getTrustedLiveInRegs() can be + // seen as a reasonable target-independent _approximation_ of "the LR", these + // are *exactly* those registers used by SrcSafetyAnalysis to initialize the + // set of trusted registers on function entry. + // Thus, this function basically checks that the precondition expected to be + // imposed by a function call instruction (which is hardcoded into the target- + // specific getTrustedLiveInRegs() function) is also respected on tail calls. + SmallVector RegsToCheck = BC.MIB->getTrustedLiveInRegs(); + LLVM_DEBUG({ + traceInst(BC, "Found tail call inst", Inst); + traceRegMask(BC, "Trusted regs", S.TrustedRegs); + }); + + // In musl on AArch64, the _start function sets LR to zero and calls the next + // stage initialization function at the end, something along these lines: + // + // _start: + // mov x30, #0 + // ; ... other initialization ... + // b _start_c ; performs "exit" system call at some point + // + // As this would produce a false positive for every executable linked with + // such libc, ignore tail calls performed by ELF entry function. + if (BC.StartFunctionAddress && + *BC.StartFunctionAddress == Inst.getFunction()->getAddress()) { + LLVM_DEBUG({ dbgs() << " Skipping tail call in ELF entry function.\n"; }); + return std::nullopt; + } + + // Returns at most one report per instruction - this is probably OK... + for (auto Reg : RegsToCheck) + if (!S.TrustedRegs[Reg]) + return make_gadget_report(UntrustedLRKind, Inst, Reg); + + return std::nullopt; +} + static std::optional> shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst, const SrcState &S) { @@ -808,15 +1448,35 @@ shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst, return make_gadget_report(SigningOracleKind, Inst, *SignedReg); } -template static void iterateOverInstrs(BinaryFunction &BF, T Fn) { - if (BF.hasCFG()) { - for (BinaryBasicBlock &BB : BF) - for (int64_t I = 0, E = BB.size(); I < E; ++I) - Fn(MCInstInBBReference(&BB, I)); - } else { - for (auto I : BF.instrs()) - Fn(MCInstInBFReference(&BF, I.first)); +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); } static SmallVector @@ -839,14 +1499,60 @@ void FunctionAnalysisContext::findUnsafeUses( BF.dump(); }); + bool UnreachableBBReported = false; + if (BF.hasCFG()) { + // Warn on basic blocks being unreachable according to BOLT (at most once + // per BinaryFunction), as this likely means the CFG reconstructed by BOLT + // is imprecise. A basic block can be + // * reachable from an entry basic block - a hopefully correct non-empty + // state is propagated to that basic block sooner or later. All basic + // blocks are expected to belong to this category under normal conditions. + // * reachable from a "directly unreachable" BB (a basic block that has no + // direct predecessors and this is not because it is an entry BB) - *some* + // non-empty state is propagated to this basic block sooner or later, as + // the initial state of directly unreachable basic blocks is + // pessimistically initialized to "all registers are unsafe" + // - a warning can be printed for the "directly unreachable" basic block + // * neither reachable from an entry nor from a "directly unreachable" BB + // (such as if this BB is in an isolated loop of basic blocks) - the final + // state is computed to be empty for this basic block + // - a warning can be printed for this basic block + for (BinaryBasicBlock &BB : BF) { + MCInst *FirstInst = BB.getFirstNonPseudoInstr(); + // Skip empty basic block early for simplicity. + if (!FirstInst) + continue; + + bool IsDirectlyUnreachable = BB.pred_empty() && !BB.isEntryPoint(); + bool HasNoStateComputed = Analysis->getStateBefore(*FirstInst).empty(); + if (!IsDirectlyUnreachable && !HasNoStateComputed) + continue; + + // Arbitrarily attach the report to the first instruction of BB. + // This is printed as "[message] in function [name], basic block ..., + // at address ..." when the issue is reported to the user. + Reports.push_back(make_generic_report( + MCInstReference::get(FirstInst, BF), + "Warning: possibly imprecise CFG, the analysis quality may be " + "degraded in this function. According to BOLT, unreachable code is " + "found" /* in function [name]... */)); + UnreachableBBReported = true; + break; // One warning per function. + } + } + // FIXME: Warn the user about imprecise analysis when the function has no CFG + // information at all. + iterateOverInstrs(BF, [&](MCInstReference Inst) { - const SrcState &S = Analysis->getStateBefore(Inst); + if (BC.MIB->isCFI(Inst)) + return; - // If non-empty state was never propagated from the entry basic block - // to Inst, assume it to be unreachable and report a warning. + const SrcState &S = Analysis->getStateBefore(Inst); if (S.empty()) { - Reports.push_back( - make_generic_report(Inst, "Warning: unreachable instruction found")); + LLVM_DEBUG( + { traceInst(BC, "Instruction has no state, skipping", Inst); }); + assert(UnreachableBBReported && "Should be reported at least once"); + (void)UnreachableBBReported; return; } @@ -856,6 +1562,9 @@ void FunctionAnalysisContext::findUnsafeUses( if (PacRetGadgetsOnly) return; + if (auto Report = shouldReportUnsafeTailCall(BC, BF, Inst, S)) + Reports.push_back(*Report); + if (auto Report = shouldReportCallGadget(BC, Inst, S)) Reports.push_back(*Report); if (auto Report = shouldReportSigningOracle(BC, Inst, S)) @@ -889,6 +1598,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 +1670,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 +1779,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/Profile/BoltAddressTranslation.cpp b/bolt/lib/Profile/BoltAddressTranslation.cpp index a253522e4fb15..7ad4e6a2e1411 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 ade8478f556e9..88229bb31a2ad 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"), @@ -181,11 +184,21 @@ 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()) @@ -453,9 +466,7 @@ int DataAggregator::prepareToParse(StringRef Name, PerfProcessInfo &Process, return PI.ReturnCode; } -Error DataAggregator::preprocessProfile(BinaryContext &BC) { - this->BC = &BC; - +void DataAggregator::parsePerfData(BinaryContext &BC) { auto ErrorCallback = [](int ReturnCode, StringRef ErrBuf) { errs() << "PERF-ERROR: return code " << ReturnCode << "\n" << ErrBuf; exit(1); @@ -468,11 +479,6 @@ Error DataAggregator::preprocessProfile(BinaryContext &BC) { ErrorCallback(ReturnCode, ErrBuf); }; - if (opts::ReadPreAggregated) { - parsePreAggregated(); - goto heatmap; - } - if (std::optional FileBuildID = BC.getFileBuildID()) { outs() << "BOLT-INFO: binary build-id is: " << *FileBuildID << "\n"; processFileBuildID(*FileBuildID); @@ -521,19 +527,28 @@ Error DataAggregator::preprocessProfile(BinaryContext &BC) { << '\n'; deleteTempFiles(); +} -heatmap: - if (!opts::HeatmapMode) - return Error::success(); +Error DataAggregator::preprocessProfile(BinaryContext &BC) { + this->BC = &BC; - if (std::error_code EC = printLBRHeatMap()) - return errorCodeToError(EC); + if (opts::ReadPreAggregated) { + parsePreAggregated(); + } else { + parsePerfData(BC); + } - if (opts::HeatmapMode == opts::HeatmapModeKind::HM_Optional) - return Error::success(); + // Sort parsed traces for faster processing. + llvm::sort(Traces, llvm::less_first()); - assert(opts::HeatmapMode == opts::HeatmapModeKind::HM_Exclusive); - exit(0); + if (opts::HeatmapMode) { + if (std::error_code EC = printLBRHeatMap()) + return errorCodeToError(EC); + if (opts::HeatmapMode == opts::HeatmapModeKind::HM_Exclusive) + exit(0); + } + + return Error::success(); } Error DataAggregator::readProfile(BinaryContext &BC) { @@ -598,8 +613,7 @@ void DataAggregator::processProfile(BinaryContext &BC) { llvm::stable_sort(MemEvents.second.Data); // Release intermediate storage. - clear(BranchLBRs); - clear(FallthroughLBRs); + clear(Traces); clear(BasicSamples); clear(MemSamples); } @@ -727,50 +741,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) { Addr = 0; - return std::pair{Func, false}; + 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); @@ -780,37 +798,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; } @@ -818,51 +819,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; @@ -889,8 +876,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())) { @@ -898,10 +886,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'); } } @@ -920,9 +907,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; } @@ -1011,9 +996,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); } @@ -1219,22 +1217,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()) { } @@ -1251,23 +1252,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()) { } @@ -1277,6 +1281,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()) { } @@ -1287,11 +1292,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(); @@ -1305,6 +1312,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; @@ -1316,30 +1324,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(); } @@ -1350,7 +1367,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) { @@ -1386,12 +1403,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'; @@ -1437,22 +1451,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); - FTInfo &Info = FallthroughLBRs[Trace(TraceFrom, TraceTo)]; - if (TraceBF && TraceBF->containsAddress(LBR.From)) - ++Info.InternCount; - else - ++Info.ExternCount; - ++NumTraces; - } + uint64_t TraceTo = NextLBR ? NextLBR->From : Trace::BR_ONLY; NextLBR = &LBR; - TakenBranchInfo &Info = BranchLBRs[Trace(LBR.From, LBR.To)]; + TakenBranchInfo &Info = TraceMap[Trace{LBR.From, LBR.To, TraceTo}]; ++Info.TakenCount; Info.MispredCount += LBR.Mispred; } @@ -1527,7 +1529,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); @@ -1555,7 +1559,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; } @@ -1563,10 +1568,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"; @@ -1574,10 +1583,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); } @@ -1591,23 +1608,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(); } diff --git a/bolt/lib/Rewrite/CMakeLists.txt b/bolt/lib/Rewrite/CMakeLists.txt index c83cf36982167..775036063dd5a 100644 --- a/bolt/lib/Rewrite/CMakeLists.txt +++ b/bolt/lib/Rewrite/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS Core DebugInfoDWARF + DebugInfoDWARFLowLevel JITLink MC Object diff --git a/bolt/lib/Rewrite/DWARFRewriter.cpp b/bolt/lib/Rewrite/DWARFRewriter.cpp index 9c9bdefe08429..0c1a1bac6c72e 100644 --- a/bolt/lib/Rewrite/DWARFRewriter.cpp +++ b/bolt/lib/Rewrite/DWARFRewriter.cpp @@ -24,10 +24,10 @@ #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/DWARF/DWARFDebugAbbrev.h" #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h" -#include "llvm/DebugInfo/DWARF/DWARFExpression.h" #include "llvm/DebugInfo/DWARF/DWARFFormValue.h" #include "llvm/DebugInfo/DWARF/DWARFTypeUnit.h" #include "llvm/DebugInfo/DWARF/DWARFUnit.h" +#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h" #include "llvm/MC/MCAsmBackend.h" #include "llvm/MC/MCAssembler.h" #include "llvm/MC/MCObjectWriter.h" diff --git a/bolt/lib/Rewrite/LinuxKernelRewriter.cpp b/bolt/lib/Rewrite/LinuxKernelRewriter.cpp index 5a5e044184d0b..174721a3a0539 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 e1aa00a3d749f..96045a916232c 100644 --- a/bolt/lib/Rewrite/RewriteInstance.cpp +++ b/bolt/lib/Rewrite/RewriteInstance.cpp @@ -547,9 +547,14 @@ Error RewriteInstance::discoverStorage() { NextAvailableOffset = std::max(NextAvailableOffset, Phdr.p_offset + Phdr.p_filesz); - BC->SegmentMapInfo[Phdr.p_vaddr] = SegmentInfo{ - Phdr.p_vaddr, Phdr.p_memsz, Phdr.p_offset, - Phdr.p_filesz, Phdr.p_align, ((Phdr.p_flags & ELF::PF_X) != 0)}; + BC->SegmentMapInfo[Phdr.p_vaddr] = + SegmentInfo{Phdr.p_vaddr, + Phdr.p_memsz, + Phdr.p_offset, + Phdr.p_filesz, + Phdr.p_align, + (Phdr.p_flags & ELF::PF_X) != 0, + (Phdr.p_flags & ELF::PF_W) != 0}; if (BC->TheTriple->getArch() == llvm::Triple::x86_64 && Phdr.p_vaddr >= BinaryContext::KernelStartX86_64) BC->IsLinuxKernel = true; @@ -626,6 +631,9 @@ Error RewriteInstance::discoverStorage() { NextAvailableAddress += BC->PageAlign; } + NewTextSegmentAddress = NextAvailableAddress; + NewTextSegmentOffset = NextAvailableOffset; + if (!opts::UseGnuStack && !BC->IsLinuxKernel) { // This is where the black magic happens. Creating PHDR table in a segment // other than that containing ELF header is tricky. Some loaders and/or @@ -652,6 +660,8 @@ Error RewriteInstance::discoverStorage() { PHDRTableAddress = NextAvailableAddress; PHDRTableOffset = NextAvailableOffset; + NewTextSegmentAddress = NextAvailableAddress; + NewTextSegmentOffset = NextAvailableOffset; // Reserve space for 3 extra pheaders. unsigned Phnum = Obj.getHeader().e_phnum; @@ -664,14 +674,12 @@ Error RewriteInstance::discoverStorage() { NextAvailableAddress += Phnum * sizeof(ELF64LEPhdrTy); NextAvailableOffset += Phnum * sizeof(ELF64LEPhdrTy); - } - // Align at cache line. - NextAvailableAddress = alignTo(NextAvailableAddress, 64); - NextAvailableOffset = alignTo(NextAvailableOffset, 64); + // Align at cache line. + NextAvailableAddress = alignTo(NextAvailableAddress, 64); + NextAvailableOffset = alignTo(NextAvailableOffset, 64); + } - NewTextSegmentAddress = NextAvailableAddress; - NewTextSegmentOffset = NextAvailableOffset; BC->LayoutStartAddress = NextAvailableAddress; // Tools such as objcopy can strip section contents but leave header @@ -780,14 +788,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 +806,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 +1015,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 +1281,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 +1554,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; @@ -3869,111 +3861,138 @@ std::vector RewriteInstance::getCodeSections() { } void RewriteInstance::mapCodeSections(BOLTLinker::SectionMapper MapSection) { - if (BC->HasRelocations) { - // Map sections for functions with pre-assigned addresses. - for (BinaryFunction *InjectedFunction : BC->getInjectedBinaryFunctions()) { - const uint64_t OutputAddress = InjectedFunction->getOutputAddress(); - if (!OutputAddress) - continue; + if (!BC->HasRelocations) { + mapCodeSectionsInPlace(MapSection); + return; + } - ErrorOr FunctionSection = - InjectedFunction->getCodeSection(); - assert(FunctionSection && "function should have section"); - FunctionSection->setOutputAddress(OutputAddress); - MapSection(*FunctionSection, OutputAddress); - InjectedFunction->setImageAddress(FunctionSection->getAllocAddress()); - InjectedFunction->setImageSize(FunctionSection->getOutputSize()); - } + // Map sections for functions with pre-assigned addresses. + for (BinaryFunction *InjectedFunction : BC->getInjectedBinaryFunctions()) { + const uint64_t OutputAddress = InjectedFunction->getOutputAddress(); + if (!OutputAddress) + continue; - // Populate the list of sections to be allocated. - std::vector CodeSections = getCodeSections(); + ErrorOr FunctionSection = + InjectedFunction->getCodeSection(); + assert(FunctionSection && "function should have section"); + FunctionSection->setOutputAddress(OutputAddress); + MapSection(*FunctionSection, OutputAddress); + InjectedFunction->setImageAddress(FunctionSection->getAllocAddress()); + InjectedFunction->setImageSize(FunctionSection->getOutputSize()); + } - // Remove sections that were pre-allocated (patch sections). - llvm::erase_if(CodeSections, [](BinarySection *Section) { - return Section->getOutputAddress(); - }); - LLVM_DEBUG(dbgs() << "Code sections in the order of output:\n"; - for (const BinarySection *Section : CodeSections) - dbgs() << Section->getName() << '\n'; - ); + // Populate the list of sections to be allocated. + std::vector CodeSections = getCodeSections(); - uint64_t PaddingSize = 0; // size of padding required at the end + // Remove sections that were pre-allocated (patch sections). + llvm::erase_if(CodeSections, [](BinarySection *Section) { + return Section->getOutputAddress(); + }); + LLVM_DEBUG(dbgs() << "Code sections in the order of output:\n"; + for (const BinarySection *Section : CodeSections) dbgs() + << Section->getName() << '\n';); - // Allocate sections starting at a given Address. - auto allocateAt = [&](uint64_t Address) { - const char *LastNonColdSectionName = BC->HasWarmSection - ? BC->getWarmCodeSectionName() - : BC->getMainCodeSectionName(); - for (BinarySection *Section : CodeSections) { - Address = alignTo(Address, Section->getAlignment()); - Section->setOutputAddress(Address); - Address += Section->getOutputSize(); - - // Hugify: Additional huge page from right side due to - // weird ASLR mapping addresses (4KB aligned) - if (opts::Hugify && !BC->HasFixedLoadAddress && - Section->getName() == LastNonColdSectionName) - Address = alignTo(Address, Section->getAlignment()); - } + uint64_t PaddingSize = 0; // size of padding required at the end - // Make sure we allocate enough space for huge pages. - ErrorOr TextSection = - BC->getUniqueSectionByName(LastNonColdSectionName); - if (opts::HotText && TextSection && TextSection->hasValidSectionID()) { - uint64_t HotTextEnd = - TextSection->getOutputAddress() + TextSection->getOutputSize(); - HotTextEnd = alignTo(HotTextEnd, BC->PageAlign); - if (HotTextEnd > Address) { - PaddingSize = HotTextEnd - Address; - Address = HotTextEnd; - } - } - return Address; - }; - - // Check if we can fit code in the original .text - bool AllocationDone = false; - if (opts::UseOldText) { - const uint64_t CodeSize = - allocateAt(BC->OldTextSectionAddress) - BC->OldTextSectionAddress; + // Allocate sections starting at a given Address. + auto allocateAt = [&](uint64_t Address) { + const char *LastNonColdSectionName = BC->HasWarmSection + ? BC->getWarmCodeSectionName() + : BC->getMainCodeSectionName(); + for (BinarySection *Section : CodeSections) { + Address = alignTo(Address, Section->getAlignment()); + Section->setOutputAddress(Address); + Address += Section->getOutputSize(); + + // Hugify: Additional huge page from right side due to + // weird ASLR mapping addresses (4KB aligned) + if (opts::Hugify && !BC->HasFixedLoadAddress && + Section->getName() == LastNonColdSectionName) + Address = alignTo(Address, Section->getAlignment()); + } - if (CodeSize <= BC->OldTextSectionSize) { - BC->outs() << "BOLT-INFO: using original .text for new code with 0x" - << Twine::utohexstr(opts::AlignText) << " alignment\n"; - AllocationDone = true; - } else { - BC->errs() - << "BOLT-WARNING: original .text too small to fit the new code" - << " using 0x" << Twine::utohexstr(opts::AlignText) - << " alignment. " << CodeSize << " bytes needed, have " - << BC->OldTextSectionSize << " bytes available.\n"; - opts::UseOldText = false; + // Make sure we allocate enough space for huge pages. + ErrorOr TextSection = + BC->getUniqueSectionByName(LastNonColdSectionName); + if (opts::HotText && TextSection && TextSection->hasValidSectionID()) { + uint64_t HotTextEnd = + TextSection->getOutputAddress() + TextSection->getOutputSize(); + HotTextEnd = alignTo(HotTextEnd, BC->PageAlign); + if (HotTextEnd > Address) { + PaddingSize = HotTextEnd - Address; + Address = HotTextEnd; } } + return Address; + }; - if (!AllocationDone) - NextAvailableAddress = allocateAt(NextAvailableAddress); + // Try to allocate sections before the \p Address and return an address for + // the allocation of the first section, or 0 if [0, Address) range is not + // big enough to fit all sections. + auto allocateBefore = [&](uint64_t Address) -> uint64_t { + for (BinarySection *Section : llvm::reverse(CodeSections)) { + if (Section->getOutputSize() > Address) + return 0; + Address -= Section->getOutputSize(); + Address = alignDown(Address, Section->getAlignment()); + Section->setOutputAddress(Address); + } + return Address; + }; - // Do the mapping for ORC layer based on the allocation. - for (BinarySection *Section : CodeSections) { - LLVM_DEBUG( - dbgs() << "BOLT: mapping " << Section->getName() << " at 0x" - << Twine::utohexstr(Section->getAllocAddress()) << " to 0x" - << Twine::utohexstr(Section->getOutputAddress()) << '\n'); - MapSection(*Section, Section->getOutputAddress()); - Section->setOutputFileOffset( - getFileOffsetForAddress(Section->getOutputAddress())); + // Check if we can fit code in the original .text + bool AllocationDone = false; + if (opts::UseOldText) { + uint64_t StartAddress; + uint64_t EndAddress; + if (opts::HotFunctionsAtEnd) { + EndAddress = BC->OldTextSectionAddress + BC->OldTextSectionSize; + StartAddress = allocateBefore(EndAddress); + } else { + StartAddress = BC->OldTextSectionAddress; + EndAddress = allocateAt(BC->OldTextSectionAddress); + } + + const uint64_t CodeSize = EndAddress - StartAddress; + if (CodeSize <= BC->OldTextSectionSize) { + BC->outs() << "BOLT-INFO: using original .text for new code with 0x" + << Twine::utohexstr(opts::AlignText) << " alignment"; + if (StartAddress != BC->OldTextSectionAddress) + BC->outs() << " at 0x" << Twine::utohexstr(StartAddress); + BC->outs() << '\n'; + AllocationDone = true; + } else { + BC->errs() << "BOLT-WARNING: original .text too small to fit the new code" + << " using 0x" << Twine::utohexstr(opts::AlignText) + << " alignment. " << CodeSize << " bytes needed, have " + << BC->OldTextSectionSize << " bytes available.\n"; + opts::UseOldText = false; } + } - // Check if we need to insert a padding section for hot text. - if (PaddingSize && !opts::UseOldText) - BC->outs() << "BOLT-INFO: padding code to 0x" - << Twine::utohexstr(NextAvailableAddress) - << " to accommodate hot text\n"; + if (!AllocationDone) + NextAvailableAddress = allocateAt(NextAvailableAddress); - return; + // Do the mapping for ORC layer based on the allocation. + for (BinarySection *Section : CodeSections) { + LLVM_DEBUG(dbgs() << "BOLT: mapping " << Section->getName() << " at 0x" + << Twine::utohexstr(Section->getAllocAddress()) + << " to 0x" + << Twine::utohexstr(Section->getOutputAddress()) << '\n'); + MapSection(*Section, Section->getOutputAddress()); + Section->setOutputFileOffset( + getFileOffsetForAddress(Section->getOutputAddress())); } + // Check if we need to insert a padding section for hot text. + if (PaddingSize && !opts::UseOldText) + BC->outs() << "BOLT-INFO: padding code to 0x" + << Twine::utohexstr(NextAvailableAddress) + << " to accommodate hot text\n"; +} + +void RewriteInstance::mapCodeSectionsInPlace( + BOLTLinker::SectionMapper MapSection) { // Processing in non-relocation mode. uint64_t NewTextSectionStartAddress = NextAvailableAddress; @@ -4149,13 +4168,8 @@ void RewriteInstance::mapAllocatableSections( } if (SType == ST_READONLY) { - if (PHDRTableAddress) { - // Segment size includes the size of the PHDR area. - NewTextSegmentSize = NextAvailableAddress - PHDRTableAddress; - } else if (NewTextSegmentAddress) { - // Existing PHDR table would be updated. + if (NewTextSegmentAddress) NewTextSegmentSize = NextAvailableAddress - NewTextSegmentAddress; - } } else if (SType == ST_READWRITE) { NewWritableSegmentSize = NextAvailableAddress - NewWritableSegmentAddress; // Restore NextAvailableAddress if no new writable sections @@ -4173,6 +4187,74 @@ void RewriteInstance::updateOutputValues(const BOLTLinker &Linker) { Function->updateOutputValues(Linker); } +void RewriteInstance::updateSegmentInfo() { + // NOTE Currently .eh_frame_hdr appends to the last segment, recalculate + // last segments size based on the NextAvailableAddress variable. + if (!NewWritableSegmentSize) { + if (NewTextSegmentAddress) + NewTextSegmentSize = NextAvailableAddress - NewTextSegmentAddress; + } else { + NewWritableSegmentSize = NextAvailableAddress - NewWritableSegmentAddress; + } + + if (NewTextSegmentSize) { + SegmentInfo TextSegment = {NewTextSegmentAddress, + NewTextSegmentSize, + NewTextSegmentOffset, + NewTextSegmentSize, + BC->PageAlign, + true, + false}; + if (!opts::Instrument) { + BC->NewSegments.push_back(TextSegment); + } else { + ErrorOr Sec = + BC->getUniqueSectionByName(".bolt.instr.counters"); + assert(Sec && "expected one and only one `.bolt.instr.counters` section"); + const uint64_t Addr = Sec->getOutputAddress(); + const uint64_t Offset = Sec->getOutputFileOffset(); + const uint64_t Size = Sec->getOutputSize(); + assert(Addr > TextSegment.Address && + Addr + Size < TextSegment.Address + TextSegment.Size && + "`.bolt.instr.counters` section is expected to be included in the " + "new text segment"); + + // Set correct size for the previous header since we are breaking the + // new text segment into three segments. + uint64_t Delta = Addr - TextSegment.Address; + TextSegment.Size = Delta; + TextSegment.FileSize = Delta; + BC->NewSegments.push_back(TextSegment); + + // Create RW segment that includes the `.bolt.instr.counters` section. + SegmentInfo RWSegment = {Addr, Size, Offset, Size, BC->RegularPageSize, + false, true}; + BC->NewSegments.push_back(RWSegment); + + // Create RX segment that includes all RX sections from runtime library. + const uint64_t AddrRX = alignTo(Addr + Size, BC->RegularPageSize); + const uint64_t OffsetRX = alignTo(Offset + Size, BC->RegularPageSize); + const uint64_t SizeRX = + NewTextSegmentSize - (AddrRX - TextSegment.Address); + SegmentInfo RXSegment = { + AddrRX, SizeRX, OffsetRX, SizeRX, BC->RegularPageSize, true, false}; + BC->NewSegments.push_back(RXSegment); + } + } + + if (NewWritableSegmentSize) { + SegmentInfo DataSegmentInfo = { + NewWritableSegmentAddress, + NewWritableSegmentSize, + getFileOffsetForAddress(NewWritableSegmentAddress), + NewWritableSegmentSize, + BC->RegularPageSize, + false, + true}; + BC->NewSegments.push_back(DataSegmentInfo); + } +} + void RewriteInstance::patchELFPHDRTable() { auto ELF64LEFile = cast(InputFile); const ELFFile &Obj = ELF64LEFile->getELFFile(); @@ -4199,110 +4281,36 @@ void RewriteInstance::patchELFPHDRTable() { if (opts::Instrument) Phnum += 2; - // NOTE Currently .eh_frame_hdr appends to the last segment, recalculate - // last segments size based on the NextAvailableAddress variable. - if (!NewWritableSegmentSize) { - if (PHDRTableAddress) - NewTextSegmentSize = NextAvailableAddress - PHDRTableAddress; - else if (NewTextSegmentAddress) - NewTextSegmentSize = NextAvailableAddress - NewTextSegmentAddress; - } else { - NewWritableSegmentSize = NextAvailableAddress - NewWritableSegmentAddress; + if (BC->NewSegments.empty()) { + BC->outs() << "BOLT-INFO: not adding new segments\n"; + return; } const uint64_t SavedPos = OS.tell(); OS.seek(PHDRTableOffset); - auto createNewPhdrs = [&]() { - SmallVector NewPhdrs; - ELF64LEPhdrTy NewPhdr; - NewPhdr.p_type = ELF::PT_LOAD; - if (PHDRTableAddress) { - NewPhdr.p_offset = PHDRTableOffset; - NewPhdr.p_vaddr = PHDRTableAddress; - NewPhdr.p_paddr = PHDRTableAddress; - } else { - NewPhdr.p_offset = NewTextSegmentOffset; - NewPhdr.p_vaddr = NewTextSegmentAddress; - NewPhdr.p_paddr = NewTextSegmentAddress; - } - NewPhdr.p_filesz = NewTextSegmentSize; - NewPhdr.p_memsz = NewTextSegmentSize; - NewPhdr.p_flags = ELF::PF_X | ELF::PF_R; - NewPhdr.p_align = BC->PageAlign; - - if (!opts::Instrument) { - NewPhdrs.push_back(NewPhdr); - } else { - ErrorOr Sec = - BC->getUniqueSectionByName(".bolt.instr.counters"); - assert(Sec && "expected one and only one `.bolt.instr.counters` section"); - const uint64_t Addr = Sec->getOutputAddress(); - const uint64_t Offset = Sec->getOutputFileOffset(); - const uint64_t Size = Sec->getOutputSize(); - assert(Addr > NewPhdr.p_vaddr && - Addr + Size < NewPhdr.p_vaddr + NewPhdr.p_memsz && - "`.bolt.instr.counters` section is expected to be included in the " - "new text sgement"); - - // Set correct size for the previous header since we are breaking the - // new text segment into three segments. - uint64_t Delta = Addr - NewPhdr.p_vaddr; - NewPhdr.p_filesz = Delta; - NewPhdr.p_memsz = Delta; - NewPhdrs.push_back(NewPhdr); - - // Create a program header for a RW segment that includes the - // `.bolt.instr.counters` section only. - ELF64LEPhdrTy NewPhdrRWSegment; - NewPhdrRWSegment.p_type = ELF::PT_LOAD; - NewPhdrRWSegment.p_offset = Offset; - NewPhdrRWSegment.p_vaddr = Addr; - NewPhdrRWSegment.p_paddr = Addr; - NewPhdrRWSegment.p_filesz = Size; - NewPhdrRWSegment.p_memsz = Size; - NewPhdrRWSegment.p_flags = ELF::PF_R | ELF::PF_W; - NewPhdrRWSegment.p_align = BC->RegularPageSize; - NewPhdrs.push_back(NewPhdrRWSegment); - - // Create a program header for a RX segment that includes all the RX - // sections from runtime library. - ELF64LEPhdrTy NewPhdrRXSegment; - NewPhdrRXSegment.p_type = ELF::PT_LOAD; - const uint64_t AddrRX = alignTo(Addr + Size, BC->RegularPageSize); - const uint64_t OffsetRX = alignTo(Offset + Size, BC->RegularPageSize); - const uint64_t SizeRX = NewTextSegmentSize - (AddrRX - NewPhdr.p_paddr); - NewPhdrRXSegment.p_offset = OffsetRX; - NewPhdrRXSegment.p_vaddr = AddrRX; - NewPhdrRXSegment.p_paddr = AddrRX; - NewPhdrRXSegment.p_filesz = SizeRX; - NewPhdrRXSegment.p_memsz = SizeRX; - NewPhdrRXSegment.p_flags = ELF::PF_X | ELF::PF_R; - NewPhdrRXSegment.p_align = BC->RegularPageSize; - NewPhdrs.push_back(NewPhdrRXSegment); - } - - return NewPhdrs; + auto createPhdr = [](const SegmentInfo &SI) { + ELF64LEPhdrTy Phdr; + Phdr.p_type = ELF::PT_LOAD; + Phdr.p_offset = SI.FileOffset; + Phdr.p_vaddr = SI.Address; + Phdr.p_paddr = SI.Address; + Phdr.p_filesz = SI.FileSize; + Phdr.p_memsz = SI.Size; + Phdr.p_flags = ELF::PF_R; + if (SI.IsExecutable) + Phdr.p_flags |= ELF::PF_X; + if (SI.IsWritable) + Phdr.p_flags |= ELF::PF_W; + Phdr.p_align = SI.Alignment; + + return Phdr; }; auto writeNewSegmentPhdrs = [&]() { - if (PHDRTableAddress || NewTextSegmentSize) { - SmallVector NewPhdrs = createNewPhdrs(); - OS.write(reinterpret_cast(NewPhdrs.data()), - sizeof(ELF64LE::Phdr) * NewPhdrs.size()); - } - - if (NewWritableSegmentSize) { - ELF64LEPhdrTy NewPhdr; - NewPhdr.p_type = ELF::PT_LOAD; - NewPhdr.p_offset = getFileOffsetForAddress(NewWritableSegmentAddress); - NewPhdr.p_vaddr = NewWritableSegmentAddress; - NewPhdr.p_paddr = NewWritableSegmentAddress; - NewPhdr.p_filesz = NewWritableSegmentSize; - NewPhdr.p_memsz = NewWritableSegmentSize; - NewPhdr.p_align = BC->RegularPageSize; - NewPhdr.p_flags = ELF::PF_R | ELF::PF_W; - OS.write(reinterpret_cast(&NewPhdr), sizeof(NewPhdr)); + for (const SegmentInfo &SI : BC->NewSegments) { + ELF64LEPhdrTy Phdr = createPhdr(SI); + OS.write(reinterpret_cast(&Phdr), sizeof(Phdr)); } }; @@ -4338,11 +4346,9 @@ void RewriteInstance::patchELFPHDRTable() { case ELF::PT_GNU_STACK: if (opts::UseGnuStack) { // Overwrite the header with the new segment header. - assert(!opts::Instrument); - SmallVector NewPhdrs = createNewPhdrs(); - assert(NewPhdrs.size() == 1 && - "expect exactly one program header was created"); - NewPhdr = NewPhdrs[0]; + assert(BC->NewSegments.size() == 1 && + "Expected exactly one new segment"); + NewPhdr = createPhdr(BC->NewSegments.front()); ModdedGnuStack = true; } break; @@ -5967,8 +5973,10 @@ void RewriteInstance::rewriteFile() { addBATSection(); // Patch program header table. - if (!BC->IsLinuxKernel) + if (!BC->IsLinuxKernel) { + updateSegmentInfo(); patchELFPHDRTable(); + } // Finalize memory image of section string table. finalizeSectionStringTable(); diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index eb1d9d8a19514..0ef1377ea2766 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" @@ -179,13 +179,10 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { 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, AArch64::S_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, AArch64::S_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, AArch64::S_LO12, Ctx); case ELF::R_AARCH64_MOVW_UABS_G3: - return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G3, Ctx); + return MCSpecifierExpr::create(Expr, AArch64::S_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, AArch64::S_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, AArch64::S_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, AArch64::S_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()); @@ -1209,8 +1206,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { OI = Inst.begin() + 2; } - *OI = MCOperand::createExpr( - MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx)); + *OI = MCOperand::createExpr(MCSymbolRefExpr::create(TBB, *Ctx)); } /// Matches indirect branch patterns in AArch64 related to a jump table (JT), @@ -1636,8 +1632,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { .addImm(0)); Code.emplace_back(MCInstBuilder(AArch64::Bcc) .addImm(AArch64CC::EQ) - .addExpr(MCSymbolRefExpr::create( - Target, MCSymbolRefExpr::VK_None, *Ctx))); + .addExpr(MCSymbolRefExpr::create(Target, *Ctx))); return Code; } @@ -1659,8 +1654,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { .addImm(0)); Code.emplace_back(MCInstBuilder(AArch64::Bcc) .addImm(AArch64CC::NE) - .addExpr(MCSymbolRefExpr::create( - Target, MCSymbolRefExpr::VK_None, *Ctx))); + .addExpr(MCSymbolRefExpr::create(Target, *Ctx))); return Code; } @@ -1960,8 +1954,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { Inst.setOpcode(IsTailCall ? AArch64::B : AArch64::BL); Inst.clear(); Inst.addOperand(MCOperand::createExpr(getTargetExprFor( - Inst, MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - *Ctx, 0))); + Inst, MCSymbolRefExpr::create(Target, *Ctx), *Ctx, 0))); if (IsTailCall) convertJmpToTailCall(Inst); } @@ -2030,9 +2023,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, AArch64::S_ABS_G3, *Ctx))); Inst.addOperand(MCOperand::createImm(0x30)); Seq.emplace_back(Inst); @@ -2040,9 +2032,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, AArch64::S_ABS_G2_NC, *Ctx))); Inst.addOperand(MCOperand::createImm(0x20)); Seq.emplace_back(Inst); @@ -2050,9 +2041,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, AArch64::S_ABS_G1_NC, *Ctx))); Inst.addOperand(MCOperand::createImm(0x10)); Seq.emplace_back(Inst); @@ -2060,9 +2050,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, AArch64::S_ABS_G0_NC, *Ctx))); Inst.addOperand(MCOperand::createImm(0)); Seq.emplace_back(Inst); @@ -2235,9 +2224,8 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { MCContext *Ctx) const override { Inst.setOpcode(AArch64::B); Inst.clear(); - Inst.addOperand(MCOperand::createExpr(getTargetExprFor( - Inst, MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx), - *Ctx, 0))); + Inst.addOperand(MCOperand::createExpr( + getTargetExprFor(Inst, MCSymbolRefExpr::create(TBB, *Ctx), *Ctx, 0))); } bool shouldRecordCodeRelocation(uint32_t RelType) const override { diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp index c7d664ab09d46..10b4913b6ab7f 100644 --- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp +++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp @@ -171,8 +171,8 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { (void)Result; assert(Result && "unimplemented branch"); - Inst.getOperand(SymOpIndex) = MCOperand::createExpr( - MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx)); + Inst.getOperand(SymOpIndex) = + MCOperand::createExpr(MCSymbolRefExpr::create(TBB, *Ctx)); } IndirectBranchType analyzeIndirectBranch( @@ -233,8 +233,7 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { Inst.setOpcode(RISCV::JAL); Inst.clear(); Inst.addOperand(MCOperand::createReg(RISCV::X0)); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand(MCOperand::createExpr(MCSymbolRefExpr::create(TBB, *Ctx))); } StringRef getTrapFillValue() const override { @@ -246,8 +245,7 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { Inst.setOpcode(Opcode); Inst.clear(); Inst.addOperand(MCOperand::createExpr(MCSpecifierExpr::create( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - ELF::R_RISCV_CALL_PLT, *Ctx))); + MCSymbolRefExpr::create(Target, *Ctx), ELF::R_RISCV_CALL_PLT, *Ctx))); } void createCall(MCInst &Inst, const MCSymbol *Target, @@ -563,8 +561,7 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { Insts.emplace_back(MCInstBuilder(RISCV::BEQ) .addReg(RegNo) .addReg(RegTmp) - .addExpr(MCSymbolRefExpr::create( - Target, MCSymbolRefExpr::VK_None, *Ctx))); + .addExpr(MCSymbolRefExpr::create(Target, *Ctx))); return Insts; } @@ -663,14 +660,12 @@ class RISCVMCPlusBuilder : public MCPlusBuilder { if (IsTailCall) { Inst.addOperand(MCOperand::createReg(RISCV::X0)); Inst.addOperand(MCOperand::createExpr(getTargetExprFor( - Inst, MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - *Ctx, 0))); + Inst, MCSymbolRefExpr::create(Target, *Ctx), *Ctx, 0))); convertJmpToTailCall(Inst); } else { Inst.addOperand(MCOperand::createReg(RISCV::X1)); Inst.addOperand(MCOperand::createExpr(getTargetExprFor( - Inst, MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx), - *Ctx, 0))); + Inst, MCSymbolRefExpr::create(Target, *Ctx), *Ctx, 0))); } } diff --git a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp index b909d7fb6bf28..a91fd146fa367 100644 --- a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp +++ b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp @@ -72,9 +72,9 @@ static InstructionListType createIncMemory(const MCSymbol *Target, Insts.back().addOperand(MCOperand::createImm(1)); // ScaleAmt Insts.back().addOperand(MCOperand::createReg(X86::NoRegister)); // IndexReg - Insts.back().addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, - *Ctx))); // Displacement + Insts.back().addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Target, + *Ctx))); // Displacement Insts.back().addOperand( MCOperand::createReg(X86::NoRegister)); // AddrSegmentReg return Insts; @@ -1625,9 +1625,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { Inst.insert(Inst.begin(), MCOperand::createReg(X86::NoRegister)); // AddrSegmentReg Inst.insert(Inst.begin(), - MCOperand::createExpr( // Displacement - MCSymbolRefExpr::create(TargetLocation, - MCSymbolRefExpr::VK_None, *Ctx))); + MCOperand::createExpr( // Displacement + MCSymbolRefExpr::create(TargetLocation, *Ctx))); Inst.insert(Inst.begin(), MCOperand::createReg(X86::NoRegister)); // IndexReg Inst.insert(Inst.begin(), @@ -2420,8 +2419,7 @@ class X86MCPlusBuilder : public MCPlusBuilder { .addReg(RegNo) .addImm(Imm)); Code.emplace_back(MCInstBuilder(X86::JCC_1) - .addExpr(MCSymbolRefExpr::create( - Target, MCSymbolRefExpr::VK_None, *Ctx)) + .addExpr(MCSymbolRefExpr::create(Target, *Ctx)) .addImm(X86::COND_E)); return Code; } @@ -2432,8 +2430,7 @@ class X86MCPlusBuilder : public MCPlusBuilder { InstructionListType Code; Code.emplace_back(MCInstBuilder(X86::CMP64ri8).addReg(RegNo).addImm(Imm)); Code.emplace_back(MCInstBuilder(X86::JCC_1) - .addExpr(MCSymbolRefExpr::create( - Target, MCSymbolRefExpr::VK_None, *Ctx)) + .addExpr(MCSymbolRefExpr::create(Target, *Ctx)) .addImm(X86::COND_NE)); return Code; } @@ -2738,24 +2735,23 @@ class X86MCPlusBuilder : public MCPlusBuilder { Inst.clear(); Inst.setOpcode(X86::JMP_1); Inst.clear(); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand(MCOperand::createExpr(MCSymbolRefExpr::create(TBB, *Ctx))); } void createLongUncondBranch(MCInst &Inst, const MCSymbol *Target, MCContext *Ctx) const override { Inst.setOpcode(X86::JMP_4); Inst.clear(); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Target, *Ctx))); } void createCall(MCInst &Inst, const MCSymbol *Target, MCContext *Ctx) override { Inst.setOpcode(X86::CALL64pcrel32); Inst.clear(); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Target, *Ctx))); } void createTailCall(MCInst &Inst, const MCSymbol *Target, @@ -2779,8 +2775,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { MCContext *Ctx) const override { Inst.setOpcode(X86::JCC_1); Inst.clear(); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Target, *Ctx))); Inst.addOperand(MCOperand::createImm(CC)); } @@ -2788,8 +2784,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { MCContext *Ctx) const override { Inst.setOpcode(X86::JCC_4); Inst.clear(); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Target, *Ctx))); Inst.addOperand(MCOperand::createImm(CC)); } @@ -2798,8 +2794,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { unsigned InvCC = getInvertedCondCode(getCondCode(Inst)); assert(InvCC != X86::COND_INVALID && "invalid branch instruction"); Inst.getOperand(Info->get(Inst.getOpcode()).NumOperands - 1).setImm(InvCC); - Inst.getOperand(0) = MCOperand::createExpr( - MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx)); + Inst.getOperand(0) = + MCOperand::createExpr(MCSymbolRefExpr::create(TBB, *Ctx)); } bool replaceBranchCondition(MCInst &Inst, const MCSymbol *TBB, MCContext *Ctx, @@ -2807,8 +2803,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { if (CC == X86::COND_INVALID) return false; Inst.getOperand(Info->get(Inst.getOpcode()).NumOperands - 1).setImm(CC); - Inst.getOperand(0) = MCOperand::createExpr( - MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx)); + Inst.getOperand(0) = + MCOperand::createExpr(MCSymbolRefExpr::create(TBB, *Ctx)); return true; } @@ -2846,8 +2842,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { MCContext *Ctx) const override { assert((isCall(Inst) || isBranch(Inst)) && !isIndirectBranch(Inst) && "Invalid instruction"); - Inst.getOperand(0) = MCOperand::createExpr( - MCSymbolRefExpr::create(TBB, MCSymbolRefExpr::VK_None, *Ctx)); + Inst.getOperand(0) = + MCOperand::createExpr(MCSymbolRefExpr::create(TBB, *Ctx)); } MCPhysReg getX86R11() const override { return X86::R11; } @@ -2894,8 +2890,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { bool IsTailCall) override { Inst.clear(); Inst.setOpcode(IsTailCall ? X86::JMP_4 : X86::CALL64pcrel32); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Target, *Ctx))); if (IsTailCall) setTailCall(Inst); } @@ -2905,8 +2901,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { Seq.clear(); MCInst Inst; Inst.setOpcode(X86::JMP_1); - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx))); + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Target, *Ctx))); if (IsTailCall) setTailCall(Inst); Seq.emplace_back(Inst); @@ -3332,8 +3328,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { Target.addOperand(MCOperand::createReg(FuncAddrReg)); if (Targets[i].first) { // Is this OK? - Target.addOperand(MCOperand::createExpr(MCSymbolRefExpr::create( - Targets[i].first, MCSymbolRefExpr::VK_None, *Ctx))); + Target.addOperand(MCOperand::createExpr( + MCSymbolRefExpr::create(Targets[i].first, *Ctx))); } else { const uint64_t Addr = Targets[i].second; // Immediate address is out of sign extended 32 bit range. @@ -3409,8 +3405,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { Je.clear(); Je.setOpcode(X86::JCC_1); if (Targets[i].first) - Je.addOperand(MCOperand::createExpr(MCSymbolRefExpr::create( - Targets[i].first, MCSymbolRefExpr::VK_None, *Ctx))); + Je.addOperand(MCOperand::createExpr( + MCSymbolRefExpr::create(Targets[i].first, *Ctx))); else Je.addOperand(MCOperand::createImm(Targets[i].second)); @@ -3422,8 +3418,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { // Jump to next compare if target addresses don't match. Jne.clear(); Jne.setOpcode(X86::JCC_1); - Jne.addOperand(MCOperand::createExpr(MCSymbolRefExpr::create( - NextTarget, MCSymbolRefExpr::VK_None, *Ctx))); + Jne.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(NextTarget, *Ctx))); Jne.addOperand(MCOperand::createImm(X86::COND_NE)); // Call specific target directly. @@ -3442,8 +3438,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { CallOrJmp.setOpcode(IsTailCall ? X86::JMP_4 : X86::CALL64pcrel32); if (Targets[i].first) - CallOrJmp.addOperand(MCOperand::createExpr(MCSymbolRefExpr::create( - Targets[i].first, MCSymbolRefExpr::VK_None, *Ctx))); + CallOrJmp.addOperand(MCOperand::createExpr( + MCSymbolRefExpr::create(Targets[i].first, *Ctx))); else CallOrJmp.addOperand(MCOperand::createImm(Targets[i].second)); } @@ -3545,8 +3541,8 @@ class X86MCPlusBuilder : public MCPlusBuilder { // Jump to target if indices match JEInst.setOpcode(X86::JCC_1); - JEInst.addOperand(MCOperand::createExpr(MCSymbolRefExpr::create( - Targets[i].first, MCSymbolRefExpr::VK_None, *Ctx))); + JEInst.addOperand(MCOperand::createExpr( + MCSymbolRefExpr::create(Targets[i].first, *Ctx))); JEInst.addOperand(MCOperand::createImm(X86::COND_E)); } @@ -3571,9 +3567,9 @@ class X86MCPlusBuilder : public MCPlusBuilder { Inst.addOperand(MCOperand::createReg(X86::RIP)); // BaseReg Inst.addOperand(MCOperand::createImm(1)); // ScaleAmt Inst.addOperand(MCOperand::createReg(X86::NoRegister)); // IndexReg - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Src, MCSymbolRefExpr::VK_None, - *Ctx))); // Displacement + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Src, + *Ctx))); // Displacement Inst.addOperand(MCOperand::createReg(X86::NoRegister)); // AddrSegmentReg } @@ -3585,9 +3581,9 @@ class X86MCPlusBuilder : public MCPlusBuilder { Inst.addOperand(MCOperand::createReg(X86::RIP)); // BaseReg Inst.addOperand(MCOperand::createImm(1)); // ScaleAmt Inst.addOperand(MCOperand::createReg(X86::NoRegister)); // IndexReg - Inst.addOperand(MCOperand::createExpr( - MCSymbolRefExpr::create(Src, MCSymbolRefExpr::VK_None, - *Ctx))); // Displacement + Inst.addOperand( + MCOperand::createExpr(MCSymbolRefExpr::create(Src, + *Ctx))); // Displacement Inst.addOperand(MCOperand::createReg(X86::NoRegister)); // AddrSegmentReg } }; diff --git a/bolt/test/AArch64/r_aarch64_prelxx.s b/bolt/test/AArch64/r_aarch64_prelxx.s index 5cbe2c50b2946..39f74301cedf1 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 4994cfb541eef..8c05491e7bca0 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 e680d0d64a21f..a3d7f365304aa 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/register-fragments-bolt-symbols.s b/bolt/test/X86/register-fragments-bolt-symbols.s index c9f1859c4e8a9..20e7345541d95 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/zero-density.s b/bolt/test/X86/zero-density.s new file mode 100644 index 0000000000000..7843804ffed8c --- /dev/null +++ b/bolt/test/X86/zero-density.s @@ -0,0 +1,32 @@ +## Check that trampoline functions are excluded from density computation. + +# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o +# RUN: ld.lld %t.o -o %t +# RUN: link_fdata %s %t %t.preagg PREAGG +# RUN: llvm-strip -NLjmp %t +# RUN: perf2bolt %t -p %t.preagg --pa -o %t.fdata | FileCheck %s +# CHECK: Functions with density >= {{.*}} account for 99.00% total sample counts. +# CHECK-NOT: the output profile is empty or the --profile-density-cutoff-hot option is set too low. + + .text + .globl trampoline +trampoline: + mov main,%rax + jmpq *%rax +.size trampoline,.-trampoline +# PREAGG: f #trampoline# #trampoline# 2 + + .globl main +main: + .cfi_startproc + vmovaps %zmm31,%zmm3 + + add $0x4,%r9 + add $0x40,%r10 + dec %r14 +Ljmp: + jne main +# PREAGG: T #Ljmp# #main# #Ljmp# 10 + ret + .cfi_endproc +.size main,.-main diff --git a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s index 284f0bea607a5..8e991fade2c86 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s +++ b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s @@ -18,6 +18,10 @@ f1: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: ret @@ -40,6 +44,10 @@ f_intermediate_overwrite1: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: autiasp // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 @@ -63,6 +71,10 @@ f_intermediate_overwrite2: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: mov x30, x0 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autiasp @@ -102,6 +114,10 @@ f_intermediate_overwrite3: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: mov w30, w0 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autiasp @@ -126,6 +142,10 @@ f_nonx30_ret: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: mov x16, x30 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: mov x16, x30 @@ -215,7 +235,7 @@ f_callclobbered_calleesaved: .globl f_unreachable_instruction .type f_unreachable_instruction,@function f_unreachable_instruction: -// CHECK-LABEL: GS-PAUTH: Warning: unreachable instruction found in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address +// CHECK-LABEL: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: add x0, x1, x2 // CHECK-NOT: instructions that write to the affected registers after any authentication are: b 1f @@ -224,20 +244,33 @@ f_unreachable_instruction: ret .size f_unreachable_instruction, .-f_unreachable_instruction -// Expected false positive: without CFG, the state is reset to all-unsafe -// after an unconditional branch. +// Without CFG, the state is reset at labels, assuming every register that can +// be clobbered in the function was actually clobbered. - .globl state_is_reset_after_indirect_branch_nocfg - .type state_is_reset_after_indirect_branch_nocfg,@function -state_is_reset_after_indirect_branch_nocfg: -// CHECK-LABEL: GS-PAUTH: non-protected ret found in function state_is_reset_after_indirect_branch_nocfg, at address -// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: ret + .globl lr_untouched_nocfg + .type lr_untouched_nocfg,@function +lr_untouched_nocfg: +// CHECK-NOT: lr_untouched_nocfg + adr x2, 1f + br x2 +1: + ret + .size lr_untouched_nocfg, .-lr_untouched_nocfg + + .globl lr_clobbered_nocfg + .type lr_clobbered_nocfg,@function +lr_clobbered_nocfg: +// CHECK-LABEL: GS-PAUTH: non-protected ret found in function lr_clobbered_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: ret // CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: adr x2, 1f br x2 1: + b 2f + bl g // never executed, but affects the expected worst-case scenario +2: ret - .size state_is_reset_after_indirect_branch_nocfg, .-state_is_reset_after_indirect_branch_nocfg + .size lr_clobbered_nocfg, .-lr_clobbered_nocfg /// Now do a basic sanity check on every different Authentication instruction: @@ -312,6 +345,10 @@ f_autia1716: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autia1716 @@ -334,6 +371,10 @@ f_autib1716: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autib1716 @@ -356,6 +397,10 @@ f_autiax12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autia x12, sp @@ -378,6 +423,10 @@ f_autibx12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autib x12, sp @@ -429,6 +478,10 @@ f_autdax12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autda x12, sp @@ -451,6 +504,10 @@ f_autdbx12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autdb x12, sp @@ -502,6 +559,10 @@ f_autizax12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autiza x12 @@ -524,6 +585,10 @@ f_autizbx12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autizb x12 @@ -575,6 +640,10 @@ f_autdzax12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autdza x12 @@ -597,6 +666,10 @@ f_autdzbx12: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autdzb x12 @@ -855,6 +928,10 @@ f_autia171615: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autia171615 @@ -877,6 +954,10 @@ f_autib171615: // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // 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]+}}: bl g // CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3 // CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 // CHECK-NEXT: {{[0-9a-f]+}}: autib171615 @@ -884,3 +965,9 @@ f_autib171615: ret .size f_autib171615, .-f_autib171615 + .globl g + .type g,@function +g: + nop + ret + .size g, .-g 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 0000000000000..c314bc7cfe5a3 --- /dev/null +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-authentication-oracles.s @@ -0,0 +1,792 @@ +// 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 + + .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 + adr x2, 1f + br x2 +1: + autia x0, x1 + ldr x2, [x0] + + 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 + adr x2, 1f + br x2 +1: + autia x0, x1 + ldr x0, [x0] + + 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: + adr x2, 1f + br x2 +1: + autia x0, x1 + + 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 + adr x2, 1f + br x2 +1: + autia x0, x1 + mul x3, x0, x1 + ldr x2, [x0] + + 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 + adr x2, 1f + br x2 +1: + autia x0, x1 + mul w3, w0, w1 + ldr x2, [x0] + + 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 + adr x2, 1f + br x2 +1: + autia x0, x1 + movk x0, #42, lsl #16 // does not overwrite x0 completely + ldr x2, [x0] + + 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 + adr x2, 1f + br x2 +1: + autia x0, x1 + mov x0, #42 + + 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 + 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 + + 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-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s index c79c5926a05cd..fb0bc7cff2377 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s @@ -1428,6 +1428,90 @@ printed_instrs_nocfg: br x0 .size printed_instrs_nocfg, .-printed_instrs_nocfg +// Test handling of unreachable basic blocks. +// +// Basic blocks without any predecessors were observed in real-world optimized +// code. At least sometimes they were actually reachable via jump table, which +// was not detected, but the function was processed as if its CFG was +// reconstructed successfully. +// +// As a more predictable model example, let's use really unreachable code +// for testing. + + .globl bad_unreachable_call + .type bad_unreachable_call,@function +bad_unreachable_call: +// CHECK-LABEL: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function bad_unreachable_call, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x0 +// CHECK-NOT: instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_unreachable_call, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x0 +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + b 1f + // unreachable basic block: + blr x0 + +1: // reachable basic block: + ldp x29, x30, [sp], #16 + autiasp + ret + .size bad_unreachable_call, .-bad_unreachable_call + + .globl good_unreachable_call + .type good_unreachable_call,@function +good_unreachable_call: +// CHECK-NOT: non-protected call{{.*}}good_unreachable_call +// CHECK-LABEL: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function good_unreachable_call, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NOT: instructions that write to the affected registers after any authentication are: +// CHECK-NOT: non-protected call{{.*}}good_unreachable_call + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + + b 1f + // unreachable basic block: + autia x0, x1 + blr x0 // <-- this call is definitely protected provided at least + // basic block boundaries are detected correctly + +1: // reachable basic block: + ldp x29, x30, [sp], #16 + autiasp + ret + .size good_unreachable_call, .-good_unreachable_call + + .globl unreachable_loop_of_bbs + .type unreachable_loop_of_bbs,@function +unreachable_loop_of_bbs: +// CHECK-NOT: unreachable basic blocks{{.*}}unreachable_loop_of_bbs +// CHECK-NOT: non-protected call{{.*}}unreachable_loop_of_bbs +// CHECK-LABEL: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function unreachable_loop_of_bbs, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x0 +// CHECK-NOT: unreachable basic blocks{{.*}}unreachable_loop_of_bbs +// CHECK-NOT: non-protected call{{.*}}unreachable_loop_of_bbs + paciasp + stp x29, x30, [sp, #-16]! + mov x29, sp + b .Lreachable_epilogue_bb + +.Lfirst_unreachable_bb: + blr x0 // <-- this call is not analyzed + b .Lsecond_unreachable_bb +.Lsecond_unreachable_bb: + blr x1 // <-- this call is not analyzed + b .Lfirst_unreachable_bb + +.Lreachable_epilogue_bb: + ldp x29, x30, [sp], #16 + autiasp + ret + .size unreachable_loop_of_bbs, .-unreachable_loop_of_bbs + .globl main .type main,@function 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 82494d834a15c..b1cec7f92ad05 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]*$}} @@ -199,8 +199,8 @@ nocfg: // CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( br x0, src-state) // CHECK-NEXT: .. result: (src-state) // CHECK-NEXT: Due to label, resetting the state before: 00000000: ret # Offset: 8 -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state) -// CHECK-NEXT: .. result: (src-state) +// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state) +// CHECK-NEXT: .. result: (src-state) // CHECK-NEXT: After src register safety analysis: // CHECK-NEXT: Binary Function "nocfg" { // CHECK-NEXT: Number : 3 @@ -220,36 +220,120 @@ 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-EMPTY: -// CHECK-NEXT: Running detailed src register safety analysis... -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( adr x0, __ENTRY_nocfg@0x[[ENTRY_ADDR]], src-state) -// CHECK-NEXT: .. result: (src-state) -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( br x0, src-state) -// CHECK-NEXT: .. result: (src-state) -// CHECK-NEXT: Due to label, resetting the state before: 00000000: ret # Offset: 8 -// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( ret x30, src-state) -// CHECK-NEXT: .. result: (src-state) -// CHECK-NEXT: After detailed src register safety analysis: -// CHECK-NEXT: Binary Function "nocfg" { -// CHECK-NEXT: Number : 3 +// CHECK-NEXT: SafeToDerefRegs: LR W30 W30_HI{{[ \t]*$}} + + .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: Secondary Entry Points : __ENTRY_nocfg@0x[[ENTRY_ADDR]] +// CHECK: BB Layout : [[BB0:[0-9a-zA-Z.]+]] // CHECK-NEXT: } -// CHECK-NEXT: .{{[A-Za-z0-9]+}}: -// CHECK-NEXT: 00000000: adr x0, __ENTRY_nocfg@0x[[ENTRY_ADDR]] # CFGUnawareSrcSafetyAnalysis: src-state -// CHECK-NEXT: 00000004: br x0 # UNKNOWN CONTROL FLOW # Offset: 4 # CFGUnawareSrcSafetyAnalysis: src-state -// CHECK-NEXT: __ENTRY_nocfg@0x[[ENTRY_ADDR]] (Entry Point): -// CHECK-NEXT: .{{[A-Za-z0-9]+}}: -// CHECK-NEXT: 00000008: ret # Offset: 8 # CFGUnawareSrcSafetyAnalysis: src-state +// 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 "nocfg" +// CHECK-NEXT: End of Function "auth_oracle" // CHECK-EMPTY: -// CHECK-NEXT: Attaching clobbering info to: 00000000: ret # Offset: 8 # CFGUnawareSrcSafetyAnalysis: src-state +// 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 diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s b/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s index 334a4108d8ab8..3a4d383ec5bc6 100644 --- a/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-signing-oracles.s @@ -505,21 +505,16 @@ bad_one_auted_one_checked_multi_bb: // * untrusted: not even s-t-d - from arg and from memory // * untrusted: subreg clobbered - between address materialization and use, between auth and check, between check and use // * untrusted: first checked then auted, auted then auted, checked then checked -// -// Note that it is important to sign and authenticate LR, as it is not kept -// safe-to-dereference across unconditional branches. .globl good_sign_addr_mat_nocfg .type good_sign_addr_mat_nocfg,@function good_sign_addr_mat_nocfg: // CHECK-NOT: good_sign_addr_mat_nocfg - paciasp adr x3, 1f br x3 1: adr x0, sym pacda x0, x1 - autiasp ret .size good_sign_addr_mat_nocfg, .-good_sign_addr_mat_nocfg @@ -527,14 +522,12 @@ good_sign_addr_mat_nocfg: .type good_sign_auted_checked_ldr_nocfg,@function good_sign_auted_checked_ldr_nocfg: // CHECK-NOT: good_sign_auted_checked_ldr_nocfg - paciasp adr x3, 1f br x3 1: autda x0, x2 ldr x2, [x0] pacda x0, x1 - autiasp ret .size good_sign_auted_checked_ldr_nocfg, .-good_sign_auted_checked_ldr_nocfg @@ -544,13 +537,11 @@ bad_sign_authed_unchecked_nocfg: // CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_sign_authed_unchecked_nocfg, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: - paciasp adr x3, 1f br x3 1: autda x0, x2 pacda x0, x1 - autiasp ret .size bad_sign_authed_unchecked_nocfg, .-bad_sign_authed_unchecked_nocfg @@ -560,13 +551,11 @@ bad_sign_checked_not_auted_nocfg: // CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_sign_checked_not_auted_nocfg, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: - paciasp adr x3, 1f br x3 1: ldr x2, [x0] pacda x0, x1 - autiasp ret .size bad_sign_checked_not_auted_nocfg, .-bad_sign_checked_not_auted_nocfg @@ -576,12 +565,10 @@ bad_sign_plain_arg_nocfg: // CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_sign_plain_arg_nocfg, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: - paciasp adr x3, 1f br x3 1: pacda x0, x1 - autiasp ret .size bad_sign_plain_arg_nocfg, .-bad_sign_plain_arg_nocfg @@ -592,13 +579,11 @@ bad_sign_plain_mem_nocfg: // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: ldr x0, [x1] - paciasp adr x3, 1f br x3 1: ldr x0, [x1] pacda x0, x1 - autiasp ret .size bad_sign_plain_mem_nocfg, .-bad_sign_plain_mem_nocfg @@ -609,14 +594,12 @@ bad_clobber_between_addr_mat_and_use_nocfg: // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: mov w0, w4 - paciasp adr x3, 1f br x3 1: adr x0, sym mov w0, w4 pacda x0, x1 - autiasp ret .size bad_clobber_between_addr_mat_and_use_nocfg, .-bad_clobber_between_addr_mat_and_use_nocfg @@ -627,7 +610,6 @@ bad_clobber_between_auted_and_checked_nocfg: // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: mov w0, w4 - paciasp adr x3, 1f br x3 1: @@ -635,7 +617,6 @@ bad_clobber_between_auted_and_checked_nocfg: mov w0, w4 ldr x2, [x0] pacda x0, x1 - autiasp ret .size bad_clobber_between_auted_and_checked_nocfg, .-bad_clobber_between_auted_and_checked_nocfg @@ -646,7 +627,6 @@ bad_clobber_between_checked_and_used_nocfg: // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: // CHECK-NEXT: 1. {{[0-9a-f]+}}: mov w0, w4 - paciasp adr x3, 1f br x3 1: @@ -654,7 +634,6 @@ bad_clobber_between_checked_and_used_nocfg: ldr x2, [x0] mov w0, w4 pacda x0, x1 - autiasp ret .size bad_clobber_between_checked_and_used_nocfg, .-bad_clobber_between_checked_and_used_nocfg @@ -664,14 +643,12 @@ bad_transition_check_then_auth_nocfg: // CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_transition_check_then_auth_nocfg, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: - paciasp adr x3, 1f br x3 1: ldr x2, [x0] autda x0, x2 pacda x0, x1 - autiasp ret .size bad_transition_check_then_auth_nocfg, .-bad_transition_check_then_auth_nocfg @@ -681,14 +658,12 @@ bad_transition_auth_then_auth_nocfg: // CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_transition_auth_then_auth_nocfg, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: - paciasp adr x3, 1f br x3 1: autda x0, x2 autda x0, x2 pacda x0, x1 - autiasp ret .size bad_transition_auth_then_auth_nocfg, .-bad_transition_auth_then_auth_nocfg @@ -698,14 +673,12 @@ bad_transition_check_then_check_nocfg: // CHECK-LABEL: GS-PAUTH: signing oracle found in function bad_transition_check_then_check_nocfg, at address // CHECK-NEXT: The instruction is {{[0-9a-f]+}}: pacda x0, x1 // CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: - paciasp adr x3, 1f br x3 1: ldr x2, [x0] ldr x2, [x0] pacda x0, x1 - autiasp ret .size bad_transition_check_then_check_nocfg, .-bad_transition_check_then_check_nocfg diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-tail-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-tail-calls.s new file mode 100644 index 0000000000000..2d3c2f1a632ca --- /dev/null +++ b/bolt/test/binary-analysis/AArch64/gs-pauth-tail-calls.s @@ -0,0 +1,597 @@ +// RUN: %clang %cflags -Wl,--entry=_custom_start -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 + +// PACRET-NOT: untrusted link register found before tail call + + .text + + .globl callee + .type callee,@function +callee: + ret + .size callee, .-callee + + .globl good_direct_tailcall_no_clobber + .type good_direct_tailcall_no_clobber,@function +good_direct_tailcall_no_clobber: +// CHECK-NOT: good_direct_tailcall_no_clobber + b callee + .size good_direct_tailcall_no_clobber, .-good_direct_tailcall_no_clobber + + .globl good_plt_tailcall_no_clobber + .type good_plt_tailcall_no_clobber,@function +good_plt_tailcall_no_clobber: +// CHECK-NOT: good_plt_tailcall_no_clobber + b callee_ext + .size good_plt_tailcall_no_clobber, .-good_plt_tailcall_no_clobber + + .globl good_indirect_tailcall_no_clobber + .type good_indirect_tailcall_no_clobber,@function +good_indirect_tailcall_no_clobber: +// CHECK-NOT: good_indirect_tailcall_no_clobber + autia x0, x1 + br x0 + .size good_indirect_tailcall_no_clobber, .-good_indirect_tailcall_no_clobber + + .globl bad_direct_tailcall_not_auted + .type bad_direct_tailcall_not_auted,@function +bad_direct_tailcall_not_auted: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_not_auted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: b callee # TAILCALL + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + b callee + .size bad_direct_tailcall_not_auted, .-bad_direct_tailcall_not_auted + + .globl bad_plt_tailcall_not_auted + .type bad_plt_tailcall_not_auted,@function +bad_plt_tailcall_not_auted: +// FIXME: Calls via PLT are disassembled incorrectly. Nevertheless, they are +// still detected as tail calls. +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_not_auted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_not_auted # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: b bad_indirect_tailcall_not_auted # TAILCALL + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + b callee_ext + .size bad_plt_tailcall_not_auted, .-bad_plt_tailcall_not_auted + + .globl bad_indirect_tailcall_not_auted + .type bad_indirect_tailcall_not_auted,@function +bad_indirect_tailcall_not_auted: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_not_auted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: {{[0-9a-f]+}}: br x0 # TAILCALL + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autia x0, x1 + br x0 + .size bad_indirect_tailcall_not_auted, .-bad_indirect_tailcall_not_auted + + .globl bad_direct_tailcall_untrusted + .type bad_direct_tailcall_untrusted,@function +bad_direct_tailcall_untrusted: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL +// 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]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: {{[0-9a-f]+}}: b callee # TAILCALL + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + b callee + .size bad_direct_tailcall_untrusted, .-bad_direct_tailcall_untrusted + + .globl bad_plt_tailcall_untrusted + .type bad_plt_tailcall_untrusted,@function +bad_plt_tailcall_untrusted: +// FIXME: Calls via PLT are disassembled incorrectly. Nevertheless, they are +// still detected as tail calls. +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_untrusted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_plt_tailcall_untrusted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL +// 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]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted # TAILCALL + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + b callee_ext + .size bad_plt_tailcall_untrusted, .-bad_plt_tailcall_untrusted + + .globl bad_indirect_tailcall_untrusted + .type bad_indirect_tailcall_untrusted,@function +bad_indirect_tailcall_untrusted: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # TAILCALL +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: br x0 # TAILCALL +// 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]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1 +// CHECK-NEXT: {{[0-9a-f]+}}: br x0 # TAILCALL + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + autia x0, x1 + br x0 + .size bad_indirect_tailcall_untrusted, .-bad_indirect_tailcall_untrusted + + .globl good_direct_tailcall_trusted + .type good_direct_tailcall_trusted,@function +good_direct_tailcall_trusted: +// CHECK-NOT: good_direct_tailcall_trusted + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + b callee + .size good_direct_tailcall_trusted, .-good_direct_tailcall_trusted + + .globl good_plt_tailcall_trusted + .type good_plt_tailcall_trusted,@function +good_plt_tailcall_trusted: +// CHECK-NOT: good_plt_tailcall_trusted + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + b callee_ext + .size good_plt_tailcall_trusted, .-good_plt_tailcall_trusted + + .globl good_indirect_tailcall_trusted + .type good_indirect_tailcall_trusted,@function +good_indirect_tailcall_trusted: +// CHECK-NOT: good_indirect_tailcall_trusted + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + autia x0, x1 + br x0 + .size good_indirect_tailcall_trusted, .-good_indirect_tailcall_trusted + + .globl good_direct_tailcall_no_clobber_multi_bb + .type good_direct_tailcall_no_clobber_multi_bb,@function +good_direct_tailcall_no_clobber_multi_bb: +// CHECK-NOT: good_direct_tailcall_no_clobber_multi_bb + b 1f +1: + b callee + .size good_direct_tailcall_no_clobber_multi_bb, .-good_direct_tailcall_no_clobber_multi_bb + + .globl good_indirect_tailcall_no_clobber_multi_bb + .type good_indirect_tailcall_no_clobber_multi_bb,@function +good_indirect_tailcall_no_clobber_multi_bb: +// CHECK-NOT: good_indirect_tailcall_no_clobber_multi_bb + autia x0, x1 + b 1f +1: + br x0 + .size good_indirect_tailcall_no_clobber_multi_bb_multi_bb, .-good_indirect_tailcall_no_clobber_multi_bb_multi_bb + + .globl bad_direct_tailcall_not_auted_multi_bb + .type bad_direct_tailcall_not_auted_multi_bb,@function +bad_direct_tailcall_not_auted_multi_bb: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_not_auted_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + cbz x3, 1f + autiasp + ldr w2, [x30] +1: + b callee + .size bad_direct_tailcall_not_auted_multi_bb, .-bad_direct_tailcall_not_auted_multi_bb + + .globl bad_indirect_tailcall_not_auted_multi_bb + .type bad_indirect_tailcall_not_auted_multi_bb,@function +bad_indirect_tailcall_not_auted_multi_bb: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_not_auted_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + cbz x3, 1f + autiasp + ldr w2, [x30] +1: + autia x0, x1 + br x0 + .size bad_indirect_tailcall_not_auted_multi_bb, .-bad_indirect_tailcall_not_auted_multi_bb + + .globl bad_direct_tailcall_untrusted_multi_bb + .type bad_direct_tailcall_untrusted_multi_bb,@function +bad_direct_tailcall_untrusted_multi_bb: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + cbz x3, 1f + ldr w2, [x30] +1: + b callee + .size bad_direct_tailcall_untrusted_multi_bb, .-bad_direct_tailcall_untrusted_multi_bb + + .globl bad_indirect_tailcall_untrusted_multi_bb + .type bad_indirect_tailcall_untrusted_multi_bb,@function +bad_indirect_tailcall_untrusted_multi_bb: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted_multi_bb, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # UNKNOWN CONTROL FLOW +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_multi_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 + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + cbz x3, 1f + ldr w2, [x30] +1: + autia x0, x1 + br x0 + .size bad_indirect_tailcall_untrusted_multi_bb, .-bad_indirect_tailcall_untrusted_multi_bb + + .globl good_direct_tailcall_trusted_multi_bb + .type good_direct_tailcall_trusted_multi_bb,@function +good_direct_tailcall_trusted_multi_bb: +// CHECK-NOT: good_direct_tailcall_trusted_multi_bb + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + b 1f +1: + b callee + .size good_direct_tailcall_trusted_multi_bb, .-good_direct_tailcall_trusted_multi_bb + + .globl good_indirect_tailcall_trusted_multi_bb + .type good_indirect_tailcall_trusted_multi_bb,@function +good_indirect_tailcall_trusted_multi_bb: +// CHECK-NOT: good_indirect_tailcall_trusted_multi_bb + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + b 1f +1: + autia x0, x1 + br x0 + .size good_indirect_tailcall_trusted_multi_bb, .-good_indirect_tailcall_trusted_multi_bb + + .globl good_direct_tailcall_no_clobber_nocfg + .type good_direct_tailcall_no_clobber_nocfg,@function +good_direct_tailcall_no_clobber_nocfg: +// CHECK-NOT: good_direct_tailcall_no_clobber_nocfg + adr x3, 1f + br x3 +1: + b callee + .size good_direct_tailcall_no_clobber_nocfg, .-good_direct_tailcall_no_clobber_nocfg + + .globl good_plt_tailcall_no_clobber_nocfg + .type good_plt_tailcall_no_clobber_nocfg,@function +good_plt_tailcall_no_clobber_nocfg: +// CHECK-NOT: good_plt_tailcall_no_clobber_nocfg + adr x3, 1f + br x3 +1: + b callee_ext + .size good_plt_tailcall_no_clobber_nocfg, .-good_plt_tailcall_no_clobber_nocfg + + .globl good_indirect_tailcall_no_clobber_nocfg + .type good_indirect_tailcall_no_clobber_nocfg,@function +good_indirect_tailcall_no_clobber_nocfg: +// CHECK-NOT: good_indirect_tailcall_no_clobber_nocfg + adr x3, 1f + br x3 +1: + autia x0, x1 + br x0 + .size good_indirect_tailcall_no_clobber_nocfg, .-good_indirect_tailcall_no_clobber_nocfg + + .globl bad_direct_tailcall_not_auted_nocfg + .type bad_direct_tailcall_not_auted_nocfg,@function +bad_direct_tailcall_not_auted_nocfg: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_not_auted_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + b callee + .size bad_direct_tailcall_not_auted_nocfg, .-bad_direct_tailcall_not_auted_nocfg + + .globl bad_plt_tailcall_not_auted_nocfg + .type bad_plt_tailcall_not_auted_nocfg,@function +bad_plt_tailcall_not_auted_nocfg: +// FIXME: Calls via PLT are disassembled incorrectly. Nevertheless, they are +// still detected as tail calls. +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_not_auted_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_not_auted_nocfg # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + b callee_ext + .size bad_plt_tailcall_not_auted_nocfg, .-bad_plt_tailcall_not_auted_nocfg + + .globl bad_indirect_tailcall_not_auted_nocfg + .type bad_indirect_tailcall_not_auted_nocfg,@function +bad_indirect_tailcall_not_auted_nocfg: +// Known false positive: ignoring UNKNOWN CONTROL FLOW without CFG. +// CHECK-NOT: bad_indirect_tailcall_not_auted_nocfg + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + autia x0, x1 + br x0 + .size bad_indirect_tailcall_not_auted_nocfg, .-bad_indirect_tailcall_not_auted_nocfg + + .globl bad_direct_tailcall_untrusted_nocfg + .type bad_direct_tailcall_untrusted_nocfg,@function +bad_direct_tailcall_untrusted_nocfg: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_direct_tailcall_untrusted_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_direct_tailcall_untrusted_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: b callee # TAILCALL + paciasp + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + autiasp + b callee + .size bad_direct_tailcall_untrusted_nocfg, .-bad_direct_tailcall_untrusted_nocfg + + .globl bad_plt_tailcall_untrusted_nocfg + .type bad_plt_tailcall_untrusted_nocfg,@function +bad_plt_tailcall_untrusted_nocfg: +// FIXME: Calls via PLT are disassembled incorrectly. Nevertheless, they are +// still detected as tail calls. +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_plt_tailcall_untrusted_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted_nocfg # TAILCALL +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_plt_tailcall_untrusted_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: b bad_indirect_tailcall_untrusted_nocfg # TAILCALL + paciasp + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + autiasp + b callee_ext + .size bad_plt_tailcall_untrusted_nocfg, .-bad_plt_tailcall_untrusted_nocfg + + .globl bad_indirect_tailcall_untrusted_nocfg + .type bad_indirect_tailcall_untrusted_nocfg,@function +bad_indirect_tailcall_untrusted_nocfg: +// Known false negative: ignoring UNKNOWN CONTROL FLOW without CFG. +// Authentication oracle is found by a generic checker, though. +// CHECK-NOT: untrusted link register{{.*}}bad_indirect_tailcall_untrusted_nocfg +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_nocfg, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 0 instructions that leak the affected registers are: +// CHECK-NOT: untrusted link register{{.*}}bad_indirect_tailcall_untrusted_nocfg + paciasp + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + autiasp + autia x0, x1 + br x0 + .size bad_indirect_tailcall_untrusted_nocfg, .-bad_indirect_tailcall_untrusted_nocfg + + .globl good_direct_tailcall_trusted_nocfg + .type good_direct_tailcall_trusted_nocfg,@function +good_direct_tailcall_trusted_nocfg: +// CHECK-NOT: good_direct_tailcall_trusted_nocfg + paciasp + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + b callee + .size good_direct_tailcall_trusted_nocfg, .-good_direct_tailcall_trusted_nocfg + + .globl good_plt_tailcall_trusted_nocfg + .type good_plt_tailcall_trusted_nocfg,@function +good_plt_tailcall_trusted_nocfg: +// CHECK-NOT: good_plt_tailcall_trusted_nocfg + paciasp + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + b callee_ext + .size good_plt_tailcall_trusted_nocfg, .-good_plt_tailcall_trusted_nocfg + + .globl good_indirect_tailcall_trusted_nocfg + .type good_indirect_tailcall_trusted_nocfg,@function +good_indirect_tailcall_trusted_nocfg: +// CHECK-NOT: good_indirect_tailcall_trusted_nocfg + paciasp + stp x29, x30, [sp, #-0x10]! + adr x3, 1f + br x3 +1: + ldp x29, x30, [sp], #0x10 + autiasp + ldr w2, [x30] + autia x0, x1 + br x0 + .size good_indirect_tailcall_trusted_nocfg, .-good_indirect_tailcall_trusted_nocfg + +// Check Armv8.3-a fused auth+branch instructions. + + .globl good_indirect_tailcall_no_clobber_v83 + .type good_indirect_tailcall_no_clobber_v83,@function +good_indirect_tailcall_no_clobber_v83: +// CHECK-NOT: good_indirect_tailcall_no_clobber_v83 + braa x0, x1 + .size good_indirect_tailcall_no_clobber_v83, .-good_indirect_tailcall_no_clobber_v83 + + .globl bad_indirect_tailcall_untrusted_v83 + .type bad_indirect_tailcall_untrusted_v83,@function +bad_indirect_tailcall_untrusted_v83: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_indirect_tailcall_untrusted_v83, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: braa x0, x1 # TAILCALL +// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are: +// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_indirect_tailcall_untrusted_v83, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: The 1 instructions that leak the affected registers are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: braa x0, x1 # TAILCALL +// 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]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: autiasp +// CHECK-NEXT: {{[0-9a-f]+}}: braa x0, x1 # TAILCALL + paciasp + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + autiasp + braa x0, x1 + .size bad_indirect_tailcall_untrusted_v83, .-bad_indirect_tailcall_untrusted_v83 + +// Make sure ELF entry function does not generate false positive reports. +// Additionally, check that the correct entry point is read from ELF header. + + .globl _start + .type _start,@function +_start: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function _start, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: b callee # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: mov x30, #0x0 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: mov x30, #0x0 +// CHECK-NEXT: {{[0-9a-f]+}}: b callee # TAILCALL + mov x30, #0 + b callee + .size _start, .-_start + + .globl _custom_start + .type _custom_start,@function +_custom_start: +// CHECK-NOT: _custom_start + mov x30, #0 + b callee + .size _custom_start, .-_custom_start + +// Test two issues being reported for the same instruction. + + .globl bad_non_protected_indirect_tailcall_not_auted + .type bad_non_protected_indirect_tailcall_not_auted,@function +bad_non_protected_indirect_tailcall_not_auted: +// CHECK-LABEL: GS-PAUTH: untrusted link register found before tail call in function bad_non_protected_indirect_tailcall_not_auted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: ldr x0, [x1] +// CHECK-NEXT: {{[0-9a-f]+}}: br x0 # TAILCALL +// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_non_protected_indirect_tailcall_not_auted, basic block {{[^,]+}}, at address +// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x0 # TAILCALL +// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are: +// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldr x0, [x1] +// CHECK-NEXT: This happens in the following basic block: +// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]! +// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10 +// CHECK-NEXT: {{[0-9a-f]+}}: ldr x0, [x1] +// CHECK-NEXT: {{[0-9a-f]+}}: br x0 # TAILCALL + stp x29, x30, [sp, #-0x10]! + ldp x29, x30, [sp], #0x10 + ldr x0, [x1] + br x0 + .size bad_non_protected_indirect_tailcall_not_auted, .-bad_non_protected_indirect_tailcall_not_auted + + .globl main + .type main,@function +main: + mov x0, 0 + ret + .size main, .-main diff --git a/bolt/test/code-at-high-address.c b/bolt/test/code-at-high-address.c new file mode 100644 index 0000000000000..fa33c1eb342d6 --- /dev/null +++ b/bolt/test/code-at-high-address.c @@ -0,0 +1,24 @@ +// Check that llvm-bolt pushes code to higher addresses under +// --hot-functions-at-end when rewriting code in-place. + +// REQUIRES: system-linux + +// RUN: %clang %cflags -O0 %s -o %t -no-pie -Wl,-q -falign-functions=64 \ +// RUN: -nostartfiles -nostdlib -ffreestanding +// RUN: llvm-bolt %t -o %t.bolt --use-old-text --align-functions=1 \ +// RUN: --no-huge-pages --align-text=1 --use-gnu-stack --hot-functions-at-end \ +// RUN: | FileCheck %s --check-prefix=CHECK-BOLT +// RUN: llvm-readelf --sections %t.bolt | FileCheck %s + +// CHECK-BOLT: using original .text for new code with 0x1 alignment at {{.*}} + +// As .text is pushed higher, preceding .bolt.org.text should have non-zero +// size. +// CHECK: .bolt.org.text PROGBITS +// CHECK-NOT: {{ 000000 }} +// CHECK-SAME: AX +// CHECK-NEXT: .text PROGBITS + +int foo() { return 0; } + +int main() { return foo(); } diff --git a/bolt/test/link_fdata.py b/bolt/test/link_fdata.py index 5a9752068bb9f..898dce8e3fb5f 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 d5a6849b27a77..8a61d11f5825f 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 0000000000000..91f5c857fbab0 --- /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 0000000000000..e981aef553b31 --- /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: -spe is available only on AArch64. diff --git a/bolt/test/program-header.test b/bolt/test/program-header.test new file mode 100644 index 0000000000000..f5490394eb6d9 --- /dev/null +++ b/bolt/test/program-header.test @@ -0,0 +1,14 @@ +# Check that llvm-bolt does not add new segments when writing code in-place. + +REQUIRES: system-linux + +RUN: %clang %cflags %p/Inputs/hello.c -o %t -no-pie -Wl,-q -nostartfiles \ +RUN: -nostdlib -ffreestanding +RUN: llvm-bolt %t -o %t.bolt --use-old-text --align-functions=1 \ +RUN: --no-huge-pages --align-text=1 --use-gnu-stack \ +RUN: | FileCheck %s --check-prefix=CHECK-BOLT +RUN: llvm-readelf -WS %t.bolt | FileCheck %s + +CHECK-BOLT: not adding new segments + +CHECK-NOT: .bolt.org.eh_frame_hdr diff --git a/bolt/tools/driver/llvm-bolt.cpp b/bolt/tools/driver/llvm-bolt.cpp index b9836c2397b6b..cf1b31f8c0c66 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/Core/BinaryContext.cpp b/bolt/unittests/Core/BinaryContext.cpp index 3fa0851d01e5a..d7374b323c916 100644 --- a/bolt/unittests/Core/BinaryContext.cpp +++ b/bolt/unittests/Core/BinaryContext.cpp @@ -199,13 +199,13 @@ TEST_P(BinaryContextTester, BaseAddress) { // Check that base address calculation is correct for a binary with the // following segment layout: BC->SegmentMapInfo[0] = - SegmentInfo{0, 0x10e8c2b4, 0, 0x10e8c2b4, 0x1000, true}; - BC->SegmentMapInfo[0x10e8d2b4] = - SegmentInfo{0x10e8d2b4, 0x3952faec, 0x10e8c2b4, 0x3952faec, 0x1000, true}; - BC->SegmentMapInfo[0x4a3bddc0] = - SegmentInfo{0x4a3bddc0, 0x148e828, 0x4a3bbdc0, 0x148e828, 0x1000, true}; - BC->SegmentMapInfo[0x4b84d5e8] = - SegmentInfo{0x4b84d5e8, 0x294f830, 0x4b84a5e8, 0x3d3820, 0x1000, true}; + SegmentInfo{0, 0x10e8c2b4, 0, 0x10e8c2b4, 0x1000, true, false}; + BC->SegmentMapInfo[0x10e8d2b4] = SegmentInfo{ + 0x10e8d2b4, 0x3952faec, 0x10e8c2b4, 0x3952faec, 0x1000, true, false}; + BC->SegmentMapInfo[0x4a3bddc0] = SegmentInfo{ + 0x4a3bddc0, 0x148e828, 0x4a3bbdc0, 0x148e828, 0x1000, true, false}; + BC->SegmentMapInfo[0x4b84d5e8] = SegmentInfo{ + 0x4b84d5e8, 0x294f830, 0x4b84a5e8, 0x3d3820, 0x1000, true, false}; std::optional BaseAddress = BC->getBaseAddressForMapping(0x7f13f5556000, 0x10e8c000); @@ -220,13 +220,14 @@ TEST_P(BinaryContextTester, BaseAddress2) { // Check that base address calculation is correct for a binary if the // alignment in ELF file are different from pagesize. // The segment layout is as follows: - BC->SegmentMapInfo[0] = SegmentInfo{0, 0x2177c, 0, 0x2177c, 0x10000, true}; + BC->SegmentMapInfo[0] = + SegmentInfo{0, 0x2177c, 0, 0x2177c, 0x10000, true, false}; BC->SegmentMapInfo[0x31860] = - SegmentInfo{0x31860, 0x370, 0x21860, 0x370, 0x10000, true}; + SegmentInfo{0x31860, 0x370, 0x21860, 0x370, 0x10000, true, false}; BC->SegmentMapInfo[0x41c20] = - SegmentInfo{0x41c20, 0x1f8, 0x21c20, 0x1f8, 0x10000, true}; + SegmentInfo{0x41c20, 0x1f8, 0x21c20, 0x1f8, 0x10000, true, false}; BC->SegmentMapInfo[0x54e18] = - SegmentInfo{0x54e18, 0x51, 0x24e18, 0x51, 0x10000, true}; + SegmentInfo{0x54e18, 0x51, 0x24e18, 0x51, 0x10000, true, false}; std::optional BaseAddress = BC->getBaseAddressForMapping(0xaaaaea444000, 0x21000); @@ -242,13 +243,14 @@ TEST_P(BinaryContextTester, BaseAddressSegmentsSmallerThanAlignment) { // when multiple segments are close together in the ELF file (closer // than the required alignment in the process space). // See https://github.com/llvm/llvm-project/issues/109384 - BC->SegmentMapInfo[0] = SegmentInfo{0, 0x1d1c, 0, 0x1d1c, 0x10000, false}; + BC->SegmentMapInfo[0] = + SegmentInfo{0, 0x1d1c, 0, 0x1d1c, 0x10000, false, false}; BC->SegmentMapInfo[0x11d40] = - SegmentInfo{0x11d40, 0x11e0, 0x1d40, 0x11e0, 0x10000, true}; + SegmentInfo{0x11d40, 0x11e0, 0x1d40, 0x11e0, 0x10000, true, false}; BC->SegmentMapInfo[0x22f20] = - SegmentInfo{0x22f20, 0x10e0, 0x2f20, 0x1f0, 0x10000, false}; + SegmentInfo{0x22f20, 0x10e0, 0x2f20, 0x1f0, 0x10000, false, false}; BC->SegmentMapInfo[0x33110] = - SegmentInfo{0x33110, 0x89, 0x3110, 0x88, 0x10000, false}; + SegmentInfo{0x33110, 0x89, 0x3110, 0x88, 0x10000, false, false}; std::optional BaseAddress = BC->getBaseAddressForMapping(0xaaaaaaab1000, 0x1000); diff --git a/bolt/unittests/Core/MemoryMaps.cpp b/bolt/unittests/Core/MemoryMaps.cpp index b0cab5431bdd3..8eb8f8ae529b1 100644 --- a/bolt/unittests/Core/MemoryMaps.cpp +++ b/bolt/unittests/Core/MemoryMaps.cpp @@ -100,10 +100,10 @@ TEST_P(MemoryMapsTester, ParseMultipleSegments) { "[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n", Pid, Filename); - BC->SegmentMapInfo[0x11da000] = - SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true}; - BC->SegmentMapInfo[0x31d0000] = - SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0000, 0x3000000, 0x200000, true}; + BC->SegmentMapInfo[0x11da000] = SegmentInfo{ + 0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true, false}; + BC->SegmentMapInfo[0x31d0000] = SegmentInfo{ + 0x31d0000, 0x51ac82c, 0x31d0000, 0x3000000, 0x200000, true, false}; DataAggregator DA(""); BC->setFilename(Filename); @@ -131,12 +131,12 @@ TEST_P(MemoryMapsTester, MultipleSegmentsMismatchedBaseAddress) { "[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n", Pid, Filename); - BC->SegmentMapInfo[0x11da000] = - SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true}; + BC->SegmentMapInfo[0x11da000] = SegmentInfo{ + 0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true, false}; // Using '0x31d0fff' FileOffset which triggers a different base address // for this second text segment. - BC->SegmentMapInfo[0x31d0000] = - SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0fff, 0x3000000, 0x200000, true}; + BC->SegmentMapInfo[0x31d0000] = SegmentInfo{ + 0x31d0000, 0x51ac82c, 0x31d0fff, 0x3000000, 0x200000, true, false}; DataAggregator DA(""); BC->setFilename(Filename); diff --git a/bolt/unittests/Profile/CMakeLists.txt b/bolt/unittests/Profile/CMakeLists.txt index e0aa0926b49c0..ce01c6c4b949e 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 0000000000000..8d023cd7b7e74 --- /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), {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/bolt/utils/llvm-bolt-wrapper.py b/bolt/utils/llvm-bolt-wrapper.py index b9d6fad825e78..b913394bce659 100755 --- a/bolt/utils/llvm-bolt-wrapper.py +++ b/bolt/utils/llvm-bolt-wrapper.py @@ -79,7 +79,7 @@ def get_cfg(key): # perf2bolt mode -PERF2BOLT_MODE = ["-aggregate-only", "-ignore-build-id"] +PERF2BOLT_MODE = ["-aggregate-only", "-ignore-build-id", "-show-density"] # boltdiff mode BOLTDIFF_MODE = ["-diff-only", "-o", "/dev/null"] diff --git a/bolt/utils/nfc-check-setup.py b/bolt/utils/nfc-check-setup.py index 710b183505853..275ac7b886d00 100755 --- a/bolt/utils/nfc-check-setup.py +++ b/bolt/utils/nfc-check-setup.py @@ -7,6 +7,29 @@ import sys import textwrap +def get_relevant_bolt_changes(dir: str) -> str: + # Return a list of bolt source changes that are relevant to testing. + all_changes = subprocess.run( + shlex.split("git show HEAD --name-only --pretty=''"), + cwd=dir, + text=True, + stdout=subprocess.PIPE, + ) + keep_bolt = subprocess.run( + shlex.split("grep '^bolt'"), + input=all_changes.stdout, + text=True, + stdout=subprocess.PIPE, + ) + keep_relevant = subprocess.run( + shlex.split( + "grep -v -e '^bolt/docs' -e '^bolt/utils/docker' -e '^bolt/utils/dot2html'" + ), + input=keep_bolt.stdout, + text=True, + stdout=subprocess.PIPE, + ) + return keep_relevant.stdout def get_git_ref_or_rev(dir: str) -> str: # Run 'git symbolic-ref -q --short HEAD || git rev-parse --short HEAD' @@ -36,6 +59,12 @@ def main(): default=os.getcwd(), help="Path to BOLT build directory, default is current " "directory", ) + parser.add_argument( + "--check-bolt-sources", + default=False, + action="store_true", + help="Create a marker file (.llvm-bolt.changes) if any relevant BOLT sources are modified", + ) parser.add_argument( "--switch-back", default=False, @@ -71,6 +100,16 @@ def main(): # memorize the old hash for logging old_ref = get_git_ref_or_rev(source_dir) + if args.check_bolt_sources: + marker = f"{args.build_dir}/.llvm-bolt.changes" + if os.path.exists(marker): + os.remove(marker) + file_changes = get_relevant_bolt_changes(source_dir) + # Create a marker file if any relevant BOLT source files changed. + if len(file_changes) > 0: + print(f"BOLT source changes were found:\n{file_changes}") + open(marker, "a").close() + # determine whether a stash is needed stash = subprocess.run( shlex.split("git status --porcelain"), diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp index 35058abab0663..f756ae6d897c8 100644 --- a/clang-tools-extra/clang-doc/BitcodeReader.cpp +++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp @@ -92,6 +92,9 @@ 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: + case InfoType::IT_variable: + case InfoType::IT_friend: Field = IT; return llvm::Error::success(); } @@ -108,6 +111,8 @@ 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_friend: case FieldId::F_default: Field = F; return llvm::Error::success(); @@ -280,7 +285,15 @@ static llvm::Error parseRecord(const Record &R, unsigned ID, static llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, TypeInfo *I) { - return llvm::Error::success(); + switch (ID) { + case TYPE_IS_BUILTIN: + return decodeRecord(R, I->IsBuiltIn, Blob); + case TYPE_IS_TEMPLATE: + return decodeRecord(R, I->IsTemplate, Blob); + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for TypeInfo"); + } } static llvm::Error parseRecord(const Record &R, unsigned ID, @@ -290,6 +303,10 @@ static llvm::Error parseRecord(const Record &R, unsigned ID, return decodeRecord(R, I->Name, Blob); case FIELD_DEFAULT_VALUE: return decodeRecord(R, I->DefaultValue, Blob); + case FIELD_TYPE_IS_BUILTIN: + return decodeRecord(R, I->IsBuiltIn, Blob); + case FIELD_TYPE_IS_TEMPLATE: + return decodeRecord(R, I->IsTemplate, Blob); default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid field for TypeInfo"); @@ -305,6 +322,10 @@ static llvm::Error parseRecord(const Record &R, unsigned ID, return decodeRecord(R, I->Access, Blob); case MEMBER_TYPE_IS_STATIC: return decodeRecord(R, I->IsStatic, Blob); + case MEMBER_TYPE_IS_BUILTIN: + return decodeRecord(R, I->IsBuiltIn, Blob); + case MEMBER_TYPE_IS_TEMPLATE: + return decodeRecord(R, I->IsTemplate, Blob); default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid field for MemberTypeInfo"); @@ -391,6 +412,55 @@ 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"); +} + +static llvm::Error parseRecord(const Record &R, unsigned ID, + llvm::StringRef Blob, VarInfo *I) { + switch (ID) { + case VAR_USR: + return decodeRecord(R, I->USR, Blob); + case VAR_NAME: + return decodeRecord(R, I->Name, Blob); + case VAR_DEFLOCATION: + return decodeRecord(R, I->DefLoc, Blob); + case VAR_IS_STATIC: + return decodeRecord(R, I->IsStatic, Blob); + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for VarInfo"); + } +} + +static llvm::Error parseRecord(const Record &R, unsigned ID, StringRef Blob, + FriendInfo *F) { + if (ID == FRIEND_IS_CLASS) { + return decodeRecord(R, F->IsClass, Blob); + } + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for Friend"); +} + template static llvm::Expected getCommentInfo(T I) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid type cannot contain CommentInfo"); @@ -429,6 +499,14 @@ template <> llvm::Expected getCommentInfo(CommentInfo *I) { return I->Children.back().get(); } +template <> llvm::Expected getCommentInfo(ConceptInfo *I) { + return &I->Description.emplace_back(); +} + +template <> Expected getCommentInfo(VarInfo *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. @@ -458,6 +536,18 @@ template <> llvm::Error addTypeInfo(FunctionInfo *I, FieldTypeInfo &&T) { return llvm::Error::success(); } +template <> llvm::Error addTypeInfo(FriendInfo *I, FieldTypeInfo &&T) { + if (!I->Params) + I->Params.emplace(); + I->Params->emplace_back(std::move(T)); + return llvm::Error::success(); +} + +template <> llvm::Error addTypeInfo(FriendInfo *I, TypeInfo &&T) { + I->ReturnType.emplace(std::move(T)); + return llvm::Error::success(); +} + template <> llvm::Error addTypeInfo(EnumInfo *I, TypeInfo &&T) { I->BaseType = std::move(T); return llvm::Error::success(); @@ -468,12 +558,28 @@ template <> llvm::Error addTypeInfo(TypedefInfo *I, TypeInfo &&T) { return llvm::Error::success(); } +template <> llvm::Error addTypeInfo(VarInfo *I, TypeInfo &&T) { + I->Type = std::move(T); + return llvm::Error::success(); +} + template static llvm::Error addReference(T I, Reference &&R, FieldId F) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid type cannot contain Reference"); } +template <> llvm::Error addReference(VarInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_namespace: + I->Namespace.emplace_back(std::move(R)); + return llvm::Error::success(); + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "VarInfo cannot contain this Reference"); + } +} + template <> llvm::Error addReference(TypeInfo *I, Reference &&R, FieldId F) { switch (F) { case FieldId::F_type: @@ -584,6 +690,27 @@ 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 <> +llvm::Error addReference(FriendInfo *Friend, Reference &&R, FieldId F) { + if (F == FieldId::F_friend) { + Friend->Ref = std::move(R); + return llvm::Error::success(); + } + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Friend cannot contain this Reference"); +} + template static void addChild(T I, ChildInfoType &&R) { llvm::errs() << "invalid child type for info"; @@ -600,6 +727,12 @@ 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)); +} +template <> void addChild(NamespaceInfo *I, VarInfo &&R) { + I->Children.Variables.emplace_back(std::move(R)); +} // Record children: template <> void addChild(RecordInfo *I, FunctionInfo &&R) { @@ -611,6 +744,9 @@ template <> void addChild(RecordInfo *I, EnumInfo &&R) { template <> void addChild(RecordInfo *I, TypedefInfo &&R) { I->Children.Typedefs.emplace_back(std::move(R)); } +template <> void addChild(RecordInfo *I, FriendInfo &&R) { + I->Friends.emplace_back(std::move(R)); +} // Other types of children: template <> void addChild(EnumInfo *I, EnumValueInfo &&R) { @@ -649,6 +785,12 @@ 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 <> void addTemplate(FriendInfo *I, TemplateInfo &&P) { + I->Template.emplace(std::move(P)); +} // Template specializations go only into template records. template @@ -662,6 +804,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) { @@ -716,9 +866,39 @@ llvm::Error ClangDocBitcodeReader::readBlock(unsigned ID, T I) { } } +// TODO: fix inconsistentent returning of errors in add callbacks. +// Once that's fixed, we only need one handleSubBlock. +template +llvm::Error ClangDocBitcodeReader::handleSubBlock(unsigned ID, T Parent, + Callback Function) { + InfoType Info; + if (auto Err = readBlock(ID, &Info)) + return Err; + Function(Parent, std::move(Info)); + return llvm::Error::success(); +} + +template +llvm::Error ClangDocBitcodeReader::handleTypeSubBlock(unsigned ID, T Parent, + Callback Function) { + InfoType Info; + if (auto Err = readBlock(ID, &Info)) + return Err; + if (auto Err = Function(Parent, std::move(Info))) + return Err; + return llvm::Error::success(); +} + template llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { llvm::TimeTraceScope("Reducing infos", "readSubBlock"); + + static auto CreateAddFunc = [](auto AddFunc) { + return [AddFunc](auto Parent, auto Child) { + return AddFunc(Parent, std::move(Child)); + }; + }; + switch (ID) { // Blocks can only have certain types of sub blocks. case BI_COMMENT_BLOCK_ID: { @@ -730,28 +910,16 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { return llvm::Error::success(); } case BI_TYPE_BLOCK_ID: { - TypeInfo TI; - if (auto Err = readBlock(ID, &TI)) - return Err; - if (auto Err = addTypeInfo(I, std::move(TI))) - return Err; - return llvm::Error::success(); + return handleTypeSubBlock( + ID, I, CreateAddFunc(addTypeInfo)); } case BI_FIELD_TYPE_BLOCK_ID: { - FieldTypeInfo TI; - if (auto Err = readBlock(ID, &TI)) - return Err; - if (auto Err = addTypeInfo(I, std::move(TI))) - return Err; - return llvm::Error::success(); + return handleTypeSubBlock( + ID, I, CreateAddFunc(addTypeInfo)); } case BI_MEMBER_TYPE_BLOCK_ID: { - MemberTypeInfo TI; - if (auto Err = readBlock(ID, &TI)) - return Err; - if (auto Err = addTypeInfo(I, std::move(TI))) - return Err; - return llvm::Error::success(); + return handleTypeSubBlock( + ID, I, CreateAddFunc(addTypeInfo)); } case BI_REFERENCE_BLOCK_ID: { Reference R; @@ -762,60 +930,50 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { return llvm::Error::success(); } case BI_FUNCTION_BLOCK_ID: { - FunctionInfo F; - if (auto Err = readBlock(ID, &F)) - return Err; - addChild(I, std::move(F)); - return llvm::Error::success(); + return handleSubBlock( + ID, I, CreateAddFunc(addChild)); } case BI_BASE_RECORD_BLOCK_ID: { - BaseRecordInfo BR; - if (auto Err = readBlock(ID, &BR)) - return Err; - addChild(I, std::move(BR)); - return llvm::Error::success(); + return handleSubBlock( + ID, I, CreateAddFunc(addChild)); } case BI_ENUM_BLOCK_ID: { - EnumInfo E; - if (auto Err = readBlock(ID, &E)) - return Err; - addChild(I, std::move(E)); - return llvm::Error::success(); + return handleSubBlock(ID, I, + CreateAddFunc(addChild)); } case BI_ENUM_VALUE_BLOCK_ID: { - EnumValueInfo EV; - if (auto Err = readBlock(ID, &EV)) - return Err; - addChild(I, std::move(EV)); - return llvm::Error::success(); + return handleSubBlock( + ID, I, CreateAddFunc(addChild)); } case BI_TEMPLATE_BLOCK_ID: { - TemplateInfo TI; - if (auto Err = readBlock(ID, &TI)) - return Err; - addTemplate(I, std::move(TI)); - return llvm::Error::success(); + return handleSubBlock(ID, I, CreateAddFunc(addTemplate)); } case BI_TEMPLATE_SPECIALIZATION_BLOCK_ID: { - TemplateSpecializationInfo TSI; - if (auto Err = readBlock(ID, &TSI)) - return Err; - addTemplateSpecialization(I, std::move(TSI)); - return llvm::Error::success(); + return handleSubBlock( + ID, I, CreateAddFunc(addTemplateSpecialization)); } case BI_TEMPLATE_PARAM_BLOCK_ID: { - TemplateParamInfo TPI; - if (auto Err = readBlock(ID, &TPI)) - return Err; - addTemplateParam(I, std::move(TPI)); - return llvm::Error::success(); + return handleSubBlock( + ID, I, CreateAddFunc(addTemplateParam)); } case BI_TYPEDEF_BLOCK_ID: { - TypedefInfo TI; - if (auto Err = readBlock(ID, &TI)) - return Err; - addChild(I, std::move(TI)); - return llvm::Error::success(); + return handleSubBlock(ID, I, + CreateAddFunc(addChild)); + } + case BI_CONSTRAINT_BLOCK_ID: { + return handleSubBlock(ID, I, + CreateAddFunc(addConstraint)); + } + case BI_CONCEPT_BLOCK_ID: { + return handleSubBlock(ID, I, + CreateAddFunc(addChild)); + } + case BI_VAR_BLOCK_ID: { + return handleSubBlock(ID, I, CreateAddFunc(addChild)); + } + case BI_FRIEND_BLOCK_ID: { + return handleSubBlock(ID, I, + CreateAddFunc(addChild)); } default: return llvm::createStringError(llvm::inconvertibleErrorCode(), @@ -922,8 +1080,14 @@ 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); + case BI_VAR_BLOCK_ID: + return createInfo(ID); + case BI_FRIEND_BLOCK_ID: + return createInfo(ID); default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "cannot create info"); @@ -962,6 +1126,9 @@ ClangDocBitcodeReader::readBitcode() { case BI_RECORD_BLOCK_ID: case BI_ENUM_BLOCK_ID: case BI_TYPEDEF_BLOCK_ID: + case BI_CONCEPT_BLOCK_ID: + case BI_VAR_BLOCK_ID: + case BI_FRIEND_BLOCK_ID: case BI_FUNCTION_BLOCK_ID: { auto InfoOrErr = readBlockToInfo(ID); if (!InfoOrErr) diff --git a/clang-tools-extra/clang-doc/BitcodeReader.h b/clang-tools-extra/clang-doc/BitcodeReader.h index a046ee2f7a24a..4947721f0a06d 100644 --- a/clang-tools-extra/clang-doc/BitcodeReader.h +++ b/clang-tools-extra/clang-doc/BitcodeReader.h @@ -64,6 +64,13 @@ class ClangDocBitcodeReader { // Helper function to set up the appropriate type of Info. llvm::Expected> readBlockToInfo(unsigned ID); + template + llvm::Error handleSubBlock(unsigned ID, T Parent, CallbackFunction Function); + + template + llvm::Error handleTypeSubBlock(unsigned ID, T Parent, + CallbackFunction Function); + llvm::BitstreamCursor &Stream; std::optional BlockInfo; FieldId CurrentReferenceField; diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp index f8a6859169b01..3cc0d4ad332f0 100644 --- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp +++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp @@ -128,7 +128,11 @@ 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"}, + {BI_VAR_BLOCK_ID, "VarBlock"}, + {BI_FRIEND_BLOCK_ID, "FriendBlock"}}; assert(Inits.size() == BlockIdCount); for (const auto &Init : Inits) BlockIdNameMap[Init.first] = Init.second; @@ -158,9 +162,15 @@ static const llvm::IndexedMap {COMMENT_ARG, {"Arg", &genStringAbbrev}}, {FIELD_TYPE_NAME, {"Name", &genStringAbbrev}}, {FIELD_DEFAULT_VALUE, {"DefaultValue", &genStringAbbrev}}, + {FIELD_TYPE_IS_BUILTIN, {"IsBuiltin", &genBoolAbbrev}}, + {FIELD_TYPE_IS_TEMPLATE, {"IsTemplate", &genBoolAbbrev}}, {MEMBER_TYPE_NAME, {"Name", &genStringAbbrev}}, {MEMBER_TYPE_ACCESS, {"Access", &genIntAbbrev}}, {MEMBER_TYPE_IS_STATIC, {"IsStatic", &genBoolAbbrev}}, + {MEMBER_TYPE_IS_BUILTIN, {"IsBuiltin", &genBoolAbbrev}}, + {MEMBER_TYPE_IS_TEMPLATE, {"IsTemplate", &genBoolAbbrev}}, + {TYPE_IS_BUILTIN, {"IsBuiltin", &genBoolAbbrev}}, + {TYPE_IS_TEMPLATE, {"IsTemplate", &genBoolAbbrev}}, {NAMESPACE_USR, {"USR", &genSymbolIdAbbrev}}, {NAMESPACE_NAME, {"Name", &genStringAbbrev}}, {NAMESPACE_PATH, {"Path", &genStringAbbrev}}, @@ -205,7 +215,19 @@ 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}}, + {VAR_USR, {"USR", &genSymbolIdAbbrev}}, + {VAR_NAME, {"Name", &genStringAbbrev}}, + {VAR_DEFLOCATION, {"DefLocation", &genLocationAbbrev}}, + {VAR_IS_STATIC, {"IsStatic", &genBoolAbbrev}}, + {FRIEND_IS_CLASS, {"IsClass", &genBoolAbbrev}}}; + assert(Inits.size() == RecordIdCount); for (const auto &Init : Inits) { RecordIdNameMap[Init.first] = Init.second; @@ -225,12 +247,15 @@ static const std::vector>> COMMENT_PARAMNAME, COMMENT_CLOSENAME, COMMENT_SELFCLOSING, COMMENT_EXPLICIT, COMMENT_ATTRKEY, COMMENT_ATTRVAL, COMMENT_ARG}}, // Type Block - {BI_TYPE_BLOCK_ID, {}}, + {BI_TYPE_BLOCK_ID, {TYPE_IS_BUILTIN, TYPE_IS_TEMPLATE}}, // FieldType Block - {BI_FIELD_TYPE_BLOCK_ID, {FIELD_TYPE_NAME, FIELD_DEFAULT_VALUE}}, + {BI_FIELD_TYPE_BLOCK_ID, + {FIELD_TYPE_NAME, FIELD_DEFAULT_VALUE, FIELD_TYPE_IS_BUILTIN, + FIELD_TYPE_IS_TEMPLATE}}, // MemberType Block {BI_MEMBER_TYPE_BLOCK_ID, - {MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS, MEMBER_TYPE_IS_STATIC}}, + {MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS, MEMBER_TYPE_IS_STATIC, + MEMBER_TYPE_IS_BUILTIN, MEMBER_TYPE_IS_TEMPLATE}}, // Enum Block {BI_ENUM_BLOCK_ID, {ENUM_USR, ENUM_NAME, ENUM_DEFLOCATION, ENUM_LOCATION, ENUM_SCOPED}}, @@ -263,7 +288,15 @@ 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}}, + {BI_VAR_BLOCK_ID, {VAR_NAME, VAR_USR, VAR_DEFLOCATION, VAR_IS_STATIC}}, + {BI_FRIEND_BLOCK_ID, {FRIEND_IS_CLASS}}}; // AbbreviationMap @@ -446,9 +479,24 @@ void ClangDocBitcodeWriter::emitBlock(const Reference &R, FieldId Field) { emitRecord((unsigned)Field, REFERENCE_FIELD); } +void ClangDocBitcodeWriter::emitBlock(const FriendInfo &R) { + StreamSubBlockGuard Block(Stream, BI_FRIEND_BLOCK_ID); + emitBlock(R.Ref, FieldId::F_friend); + emitRecord(R.IsClass, FRIEND_IS_CLASS); + if (R.Template) + emitBlock(*R.Template); + if (R.Params) + for (const auto &P : *R.Params) + emitBlock(P); + if (R.ReturnType) + emitBlock(*R.ReturnType); +} + void ClangDocBitcodeWriter::emitBlock(const TypeInfo &T) { StreamSubBlockGuard Block(Stream, BI_TYPE_BLOCK_ID); emitBlock(T.Type, FieldId::F_type); + emitRecord(T.IsBuiltIn, TYPE_IS_BUILTIN); + emitRecord(T.IsTemplate, TYPE_IS_TEMPLATE); } void ClangDocBitcodeWriter::emitBlock(const TypedefInfo &T) { @@ -470,6 +518,8 @@ void ClangDocBitcodeWriter::emitBlock(const FieldTypeInfo &T) { emitBlock(T.Type, FieldId::F_type); emitRecord(T.Name, FIELD_TYPE_NAME); emitRecord(T.DefaultValue, FIELD_DEFAULT_VALUE); + emitRecord(T.IsBuiltIn, FIELD_TYPE_IS_BUILTIN); + emitRecord(T.IsTemplate, FIELD_TYPE_IS_TEMPLATE); } void ClangDocBitcodeWriter::emitBlock(const MemberTypeInfo &T) { @@ -478,6 +528,9 @@ void ClangDocBitcodeWriter::emitBlock(const MemberTypeInfo &T) { emitRecord(T.Name, MEMBER_TYPE_NAME); emitRecord(T.Access, MEMBER_TYPE_ACCESS); emitRecord(T.IsStatic, MEMBER_TYPE_IS_STATIC); + emitRecord(T.IsBuiltIn, MEMBER_TYPE_IS_BUILTIN); + emitRecord(T.IsTemplate, MEMBER_TYPE_IS_TEMPLATE); + emitRecord(T.IsTemplate, MEMBER_TYPE_IS_TEMPLATE); for (const auto &CI : T.Description) emitBlock(CI); } @@ -524,6 +577,10 @@ 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); + for (const auto &C : I.Children.Variables) + emitBlock(C); } void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) { @@ -587,6 +644,8 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) { emitBlock(C); if (I.Template) emitBlock(*I.Template); + for (const auto &C : I.Friends) + emitBlock(C); } void ClangDocBitcodeWriter::emitBlock(const BaseRecordInfo &I) { @@ -627,12 +686,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) { @@ -647,6 +719,26 @@ 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); +} + +void ClangDocBitcodeWriter::emitBlock(const VarInfo &I) { + StreamSubBlockGuard Block(Stream, BI_VAR_BLOCK_ID); + emitRecord(I.USR, VAR_USR); + emitRecord(I.Name, VAR_NAME); + for (const auto &N : I.Namespace) + emitBlock(N, FieldId::F_namespace); + for (const auto &CI : I.Description) + emitBlock(CI); + if (I.DefLoc) + emitRecord(*I.DefLoc, VAR_DEFLOCATION); + emitRecord(I.IsStatic, VAR_IS_STATIC); + emitBlock(I.Type); +} + bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) { switch (I->IT) { case InfoType::IT_namespace: @@ -664,6 +756,15 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) { case InfoType::IT_typedef: emitBlock(*static_cast(I)); break; + case InfoType::IT_concept: + emitBlock(*static_cast(I)); + break; + case InfoType::IT_variable: + emitBlock(*static_cast(I)); + break; + case InfoType::IT_friend: + 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 e33a1aece883c..d09ec4ca34006 100644 --- a/clang-tools-extra/clang-doc/BitcodeWriter.h +++ b/clang-tools-extra/clang-doc/BitcodeWriter.h @@ -66,7 +66,11 @@ 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_VAR_BLOCK_ID, + BI_FRIEND_BLOCK_ID, BI_LAST, BI_FIRST = BI_VERSION_BLOCK_ID }; @@ -93,11 +97,17 @@ enum RecordId { COMMENT_ATTRKEY, COMMENT_ATTRVAL, COMMENT_ARG, + TYPE_IS_BUILTIN, + TYPE_IS_TEMPLATE, FIELD_TYPE_NAME, FIELD_DEFAULT_VALUE, + FIELD_TYPE_IS_BUILTIN, + FIELD_TYPE_IS_TEMPLATE, MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS, MEMBER_TYPE_IS_STATIC, + MEMBER_TYPE_IS_BUILTIN, + MEMBER_TYPE_IS_TEMPLATE, NAMESPACE_USR, NAMESPACE_NAME, NAMESPACE_PATH, @@ -135,6 +145,16 @@ enum RecordId { TYPEDEF_NAME, TYPEDEF_DEFLOCATION, TYPEDEF_IS_USING, + CONCEPT_USR, + CONCEPT_NAME, + CONCEPT_IS_TYPE, + CONCEPT_CONSTRAINT_EXPRESSION, + CONSTRAINT_EXPRESSION, + VAR_USR, + VAR_NAME, + VAR_DEFLOCATION, + VAR_IS_STATIC, + FRIEND_IS_CLASS, RI_LAST, RI_FIRST = VERSION }; @@ -150,7 +170,9 @@ enum class FieldId { F_vparent, F_type, F_child_namespace, - F_child_record + F_child_record, + F_concept, + F_friend }; class ClangDocBitcodeWriter { @@ -179,7 +201,11 @@ 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); + void emitBlock(const FriendInfo &R); + void emitBlock(const VarInfo &B); private: class AbbreviationMap { diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 7293a129177c9..8294ff9118558 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -985,6 +985,10 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, MainContentNodes = genHTML(*static_cast(I), CDCtx, InfoTitle); break; + case InfoType::IT_concept: + case InfoType::IT_variable: + case InfoType::IT_friend: + break; case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected info type"); @@ -1011,6 +1015,12 @@ static std::string getRefType(InfoType IT) { return "enum"; case InfoType::IT_typedef: return "typedef"; + case InfoType::IT_concept: + return "concept"; + case InfoType::IT_variable: + return "variable"; + case InfoType::IT_friend: + return "friend"; } llvm_unreachable("Unknown InfoType"); } diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 69c670b208440..7aeaa1b7cf67d 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -585,6 +585,11 @@ 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_variable: + case InfoType::IT_friend: + 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 index 0f7cbafcf5135..0e1a0cc347e45 100644 --- a/clang-tools-extra/clang-doc/JSONGenerator.cpp +++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp @@ -22,13 +22,30 @@ class JSONGenerator : public Generator { 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); +static void serializeInfo(const RecordInfo &I, Object &Obj, + const std::optional &RepositoryUrl); + +static void serializeReference(const Reference &Ref, Object &ReferenceObj); + +template +static void serializeArray(const Container &Records, Object &Obj, + const std::string &Key, + SerializationFunc SerializeInfo); + +// Convenience lambda to pass to serializeArray. +// If a serializeInfo needs a RepositoryUrl, create a local lambda that captures +// the optional. +static auto SerializeInfoLambda = [](const auto &Info, Object &Object) { + serializeInfo(Info, Object); +}; +static auto SerializeReferenceLambda = [](const auto &Ref, Object &Object) { + serializeReference(Ref, Object); +}; -static json::Object serializeLocation(const Location &Loc, - std::optional RepositoryUrl) { +static json::Object +serializeLocation(const Location &Loc, + const std::optional &RepositoryUrl) { Object LocationObj = Object(); LocationObj["LineNumber"] = Loc.StartLineNumber; LocationObj["Filename"] = Loc.Filename; @@ -150,8 +167,9 @@ static json::Value serializeComment(const CommentInfo &I) { llvm_unreachable("Unknown comment kind encountered."); } -static void serializeCommonAttributes(const Info &I, json::Object &Obj, - std::optional RepositoryUrl) { +static void +serializeCommonAttributes(const Info &I, json::Object &Obj, + const std::optional &RepositoryUrl) { Obj["Name"] = I.Name; Obj["USR"] = toHex(toStringRef(I.USR)); @@ -189,63 +207,55 @@ static void serializeReference(const Reference &Ref, Object &ReferenceObj) { 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; - } +static void +serializeCommonChildren(const ScopeChildren &Children, json::Object &Obj, + const std::optional &RepositoryUrl) { + static auto SerializeInfo = [&RepositoryUrl](const auto &Info, + Object &Object) { + serializeInfo(Info, Object, RepositoryUrl); + }; + + if (!Children.Enums.empty()) + serializeArray(Children.Enums, Obj, "Enums", SerializeInfo); + + if (!Children.Typedefs.empty()) + serializeArray(Children.Typedefs, Obj, "Typedefs", SerializeInfo); + + if (!Children.Records.empty()) + serializeArray(Children.Records, Obj, "Records", SerializeReferenceLambda); +} - 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; +template +static void serializeArray(const Container &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; +} - 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; - } +static void serializeInfo(const ConstraintInfo &I, Object &Obj) { + serializeReference(I.ConceptRef, Obj); + Obj["Expression"] = I.ConstraintExpr; +} + +static void serializeInfo(const ArrayRef &Params, + Object &Obj) { + json::Value ParamsArray = Array(); + auto &ParamsArrayRef = *ParamsArray.getAsArray(); + ParamsArrayRef.reserve(Params.size()); + for (const auto &Param : Params) + ParamsArrayRef.push_back(Param.Contents); + Obj["Parameters"] = ParamsArray; } static void serializeInfo(const TemplateInfo &Template, Object &Obj) { @@ -257,29 +267,29 @@ static void serializeInfo(const TemplateInfo &Template, Object &Obj) { 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; - } + if (!Template.Specialization->Params.empty()) + serializeInfo(Template.Specialization->Params, TemplateSpecializationObj); 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.Params.empty()) + serializeInfo(Template.Params, TemplateObj); + + if (!Template.Constraints.empty()) + serializeArray(Template.Constraints, TemplateObj, "Constraints", + SerializeInfoLambda); Obj["Template"] = TemplateVal; } +static void serializeInfo(const ConceptInfo &I, Object &Obj, + const 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; @@ -288,8 +298,13 @@ static void serializeInfo(const TypeInfo &I, Object &Obj) { Obj["IsBuiltIn"] = I.IsBuiltIn; } +static void serializeInfo(const FieldTypeInfo &I, Object &Obj) { + Obj["Name"] = I.Name; + Obj["Type"] = I.Type.Name; +} + static void serializeInfo(const FunctionInfo &F, json::Object &Obj, - std::optional RepositoryURL) { + const std::optional &RepositoryURL) { serializeCommonAttributes(F, Obj, RepositoryURL); Obj["IsStatic"] = F.IsStatic; @@ -297,26 +312,23 @@ static void serializeInfo(const FunctionInfo &F, json::Object &Obj, 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.Params.empty()) + serializeArray(F.Params, Obj, "Params", SerializeInfoLambda); if (F.Template) serializeInfo(F.Template.value(), Obj); } +static void serializeInfo(const EnumValueInfo &I, Object &Obj) { + Obj["Name"] = I.Name; + if (!I.ValueExpr.empty()) + Obj["ValueExpr"] = I.ValueExpr; + else + Obj["Value"] = I.Value; +} + static void serializeInfo(const EnumInfo &I, json::Object &Obj, - std::optional RepositoryUrl) { + const std::optional &RepositoryUrl) { serializeCommonAttributes(I, Obj, RepositoryUrl); Obj["Scoped"] = I.Scoped; @@ -329,26 +341,12 @@ static void serializeInfo(const EnumInfo &I, json::Object &Obj, 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; - } + if (!I.Members.empty()) + serializeArray(I.Members, Obj, "Members", SerializeInfoLambda); } static void serializeInfo(const TypedefInfo &I, json::Object &Obj, - std::optional RepositoryUrl) { + const std::optional &RepositoryUrl) { serializeCommonAttributes(I, Obj, RepositoryUrl); Obj["TypeDeclaration"] = I.TypeDeclaration; Obj["IsUsing"] = I.IsUsing; @@ -358,8 +356,32 @@ static void serializeInfo(const TypedefInfo &I, json::Object &Obj, Obj["Underlying"] = TypeVal; } +static void serializeInfo(const BaseRecordInfo &I, Object &Obj, + const std::optional &RepositoryUrl) { + serializeInfo(static_cast(I), Obj, RepositoryUrl); + Obj["IsVirtual"] = I.IsVirtual; + Obj["Access"] = getAccessSpelling(I.Access); + Obj["IsParent"] = I.IsParent; +} + +static void serializeInfo(const FriendInfo &I, Object &Obj) { + auto FriendRef = Object(); + serializeReference(I.Ref, FriendRef); + Obj["Reference"] = std::move(FriendRef); + Obj["IsClass"] = I.IsClass; + if (I.Template) + serializeInfo(I.Template.value(), Obj); + if (I.Params) + serializeArray(I.Params.value(), Obj, "Params", SerializeInfoLambda); + if (I.ReturnType) { + auto ReturnTypeObj = Object(); + serializeInfo(I.ReturnType.value(), ReturnTypeObj); + Obj["ReturnType"] = std::move(ReturnTypeObj); + } +} + static void serializeInfo(const RecordInfo &I, json::Object &Obj, - std::optional RepositoryUrl) { + const std::optional &RepositoryUrl) { serializeCommonAttributes(I, Obj, RepositoryUrl); Obj["FullName"] = I.FullName; Obj["TagType"] = getTagType(I.TagType); @@ -412,63 +434,59 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj, 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.Bases.empty()) + serializeArray( + I.Bases, Obj, "Bases", + [&RepositoryUrl](const BaseRecordInfo &Base, Object &BaseObj) { + serializeInfo(Base, BaseObj, RepositoryUrl); + }); if (!I.Parents.empty()) - serializeReference(I.Parents, Obj, "Parents"); + serializeArray(I.Parents, Obj, "Parents", SerializeReferenceLambda); if (!I.VirtualParents.empty()) - serializeReference(I.VirtualParents, Obj, "VirtualParents"); + serializeArray(I.VirtualParents, Obj, "VirtualParents", + SerializeReferenceLambda); if (I.Template) serializeInfo(I.Template.value(), Obj); + if (!I.Friends.empty()) + serializeArray(I.Friends, Obj, "Friends", SerializeInfoLambda); + serializeCommonChildren(I.Children, Obj, RepositoryUrl); } +static void serializeInfo(const VarInfo &I, json::Object &Obj, + const std::optional &RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["IsStatic"] = I.IsStatic; + auto TypeObj = Object(); + serializeInfo(I.Type, TypeObj); + Obj["Type"] = std::move(TypeObj); +} + static void serializeInfo(const NamespaceInfo &I, json::Object &Obj, - std::optional RepositoryUrl) { + const 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; - } + if (!I.Children.Namespaces.empty()) + serializeArray(I.Children.Namespaces, Obj, "Namespaces", + SerializeReferenceLambda); - 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; - } + static auto SerializeInfo = [&RepositoryUrl](const auto &Info, + Object &Object) { + serializeInfo(Info, Object, RepositoryUrl); + }; + + if (!I.Children.Functions.empty()) + serializeArray(I.Children.Functions, Obj, "Functions", SerializeInfo); + + if (!I.Children.Concepts.empty()) + serializeArray(I.Children.Concepts, Obj, "Concepts", SerializeInfo); + + if (!I.Children.Variables.empty()) + serializeArray(I.Children.Variables, Obj, "Variables", SerializeInfo); serializeCommonChildren(I.Children, Obj, RepositoryUrl); } @@ -520,9 +538,12 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS, 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: + case InfoType::IT_variable: + case InfoType::IT_friend: break; case InfoType::IT_default: return createStringError(inconvertibleErrorCode(), "unexpected info type"); diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp index 2becccf8b07da..6f16f5bd2f528 100644 --- a/clang-tools-extra/clang-doc/MDGenerator.cpp +++ b/clang-tools-extra/clang-doc/MDGenerator.cpp @@ -372,6 +372,15 @@ static llvm::Error genIndex(ClangDocContext &CDCtx) { case InfoType::IT_typedef: Type = "Typedef"; break; + case InfoType::IT_concept: + Type = "Concept"; + break; + case InfoType::IT_variable: + Type = "Variable"; + break; + case InfoType::IT_friend: + Type = "Friend"; + break; case InfoType::IT_default: Type = "Other"; } @@ -464,6 +473,10 @@ 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: + case InfoType::IT_variable: + case InfoType::IT_friend: + 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 9f640b5325da4..497b80cf97463 100644 --- a/clang-tools-extra/clang-doc/Mapper.cpp +++ b/clang-tools-extra/clang-doc/Mapper.cpp @@ -134,6 +134,16 @@ bool MapASTVisitor::VisitTypeAliasDecl(const TypeAliasDecl *D) { return mapDecl(D, /*isDefinition=*/true); } +bool MapASTVisitor::VisitConceptDecl(const ConceptDecl *D) { + return mapDecl(D, true); +} + +bool MapASTVisitor::VisitVarDecl(const VarDecl *D) { + if (D->isCXXClassMember()) + return true; + return mapDecl(D, D->isThisDeclarationADefinition()); +} + 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 36322ea2bfb77..322df6d594b3d 100644 --- a/clang-tools-extra/clang-doc/Mapper.h +++ b/clang-tools-extra/clang-doc/Mapper.h @@ -41,6 +41,8 @@ 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); + bool VisitVarDecl(const VarDecl *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 71a926f1c73e0..422a76d99e5b3 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -143,6 +143,12 @@ mergeInfos(std::vector> &Values) { return reduce(Values); case InfoType::IT_typedef: return reduce(Values); + case InfoType::IT_concept: + return reduce(Values); + case InfoType::IT_variable: + return reduce(Values); + case InfoType::IT_friend: + return reduce(Values); case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected info type"); @@ -243,6 +249,15 @@ void Reference::merge(Reference &&Other) { Path = Other.Path; } +bool FriendInfo::mergeable(const FriendInfo &Other) { + return Ref.USR == Other.Ref.USR && Ref.Name == Other.Ref.Name; +} + +void FriendInfo::merge(FriendInfo &&Other) { + assert(mergeable(Other)); + Ref.merge(std::move(Other.Ref)); +} + void Info::mergeBase(Info &&Other) { assert(mergeable(Other)); if (USR == EmptySID) @@ -288,6 +303,8 @@ 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)); + reduceChildren(Children.Variables, std::move(Other.Children.Variables)); mergeBase(std::move(Other)); } @@ -307,6 +324,8 @@ void RecordInfo::merge(RecordInfo &&Other) { Parents = std::move(Other.Parents); if (VirtualParents.empty()) VirtualParents = std::move(Other.VirtualParents); + if (Friends.empty()) + Friends = std::move(Other.Friends); // Reduce children if necessary. reduceChildren(Children.Records, std::move(Other.Children.Records)); reduceChildren(Children.Functions, std::move(Other.Children.Functions)); @@ -352,6 +371,28 @@ 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)); +} + +void VarInfo::merge(VarInfo &&Other) { + assert(mergeable(Other)); + if (!IsStatic) + IsStatic = Other.IsStatic; + if (Type.Type.USR == EmptySID && Type.Type.Name == "") + Type = std::move(Other.Type); + SymbolInfo::merge(std::move(Other)); +} + BaseRecordInfo::BaseRecordInfo() : RecordInfo() {} BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, @@ -388,6 +429,15 @@ 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_variable: + return llvm::SmallString<16>("@nonymous_variable_" + + toHex(llvm::toStringRef(USR))); + case InfoType::IT_friend: + return llvm::SmallString<16>("@nonymous_friend_" + + toHex(llvm::toStringRef(USR))); case InfoType::IT_default: return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR))); } @@ -453,6 +503,8 @@ 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()); + llvm::sort(Variables.begin(), Variables.end()); } } // namespace doc } // namespace clang diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h index 75da500645819..fe5cc48069d58 100644 --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -35,6 +35,8 @@ struct EnumInfo; struct FunctionInfo; struct Info; struct TypedefInfo; +struct ConceptInfo; +struct VarInfo; enum class InfoType { IT_default, @@ -42,7 +44,10 @@ enum class InfoType { IT_record, IT_function, IT_enum, - IT_typedef + IT_typedef, + IT_concept, + IT_variable, + IT_friend }; enum class CommentKind { @@ -166,6 +171,8 @@ struct ScopeChildren { std::vector Functions; std::vector Enums; std::vector Typedefs; + std::vector Concepts; + std::vector Variables; void sort(); }; @@ -211,6 +218,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 { @@ -219,6 +235,7 @@ struct TemplateInfo { // Set when this is a specialization of another record/function. std::optional Specialization; + std::vector Constraints; }; // Info for field types. @@ -363,6 +380,31 @@ struct SymbolInfo : public Info { bool IsStatic = false; }; +struct FriendInfo : SymbolInfo { + FriendInfo() : SymbolInfo(InfoType::IT_friend) {} + FriendInfo(SymbolID USR) : SymbolInfo(InfoType::IT_friend, USR) {} + FriendInfo(const InfoType IT, const SymbolID &USR, + const StringRef Name = StringRef()) + : SymbolInfo(IT, USR, Name) {} + bool mergeable(const FriendInfo &Other); + void merge(FriendInfo &&Other); + + Reference Ref; + std::optional Template; + std::optional ReturnType; + std::optional> Params; + bool IsClass = false; +}; + +struct VarInfo : SymbolInfo { + VarInfo() : SymbolInfo(InfoType::IT_variable) {} + explicit VarInfo(SymbolID USR) : SymbolInfo(InfoType::IT_variable, USR) {} + + void merge(VarInfo &&I); + + TypeInfo Type; +}; + // TODO: Expand to allow for documenting templating and default args. // Info for functions. struct FunctionInfo : public SymbolInfo { @@ -429,6 +471,8 @@ struct RecordInfo : public SymbolInfo { Bases; // List of base/parent records; this includes inherited methods and // attributes + std::vector Friends; + ScopeChildren Children; }; @@ -513,6 +557,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 820e8bfd8e644..6cc372ce98a6d 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -8,8 +8,10 @@ #include "Serialize.h" #include "BitcodeWriter.h" + #include "clang/AST/Attr.h" #include "clang/AST/Comment.h" +#include "clang/AST/DeclFriend.h" #include "clang/Index/USRGeneration.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/StringExtras.h" @@ -21,6 +23,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)); } @@ -388,6 +401,11 @@ std::string serialize(std::unique_ptr &I) { return serialize(*static_cast(I.get())); case InfoType::IT_function: return serialize(*static_cast(I.get())); + case InfoType::IT_concept: + return serialize(*static_cast(I.get())); + case InfoType::IT_variable: + return serialize(*static_cast(I.get())); + case InfoType::IT_friend: case InfoType::IT_typedef: case InfoType::IT_default: return ""; @@ -491,6 +509,14 @@ 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)); +} + +static void InsertChild(ScopeChildren &Scope, VarInfo Info) { + Scope.Variables.push_back(std::move(Info)); +} + // Creates a parent of the correct type for the given child and inserts it into // that parent. // @@ -531,6 +557,9 @@ static std::unique_ptr makeAndInsertIntoParent(ChildType Child) { case InfoType::IT_enum: case InfoType::IT_function: case InfoType::IT_typedef: + case InfoType::IT_concept: + case InfoType::IT_variable: + case InfoType::IT_friend: break; } llvm_unreachable("Invalid reference type for parent namespace"); @@ -740,6 +769,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) { @@ -751,6 +824,8 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *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 = @@ -876,6 +951,55 @@ emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc, return {std::move(NSI), makeAndInsertIntoParent(*NSI)}; } +static void parseFriends(RecordInfo &RI, const CXXRecordDecl *D) { + if (!D->hasDefinition() || !D->hasFriends()) + return; + + for (const FriendDecl *FD : D->friends()) { + if (FD->isUnsupportedFriend()) + continue; + + FriendInfo F(InfoType::IT_friend, getUSRForDecl(FD)); + const auto *ActualDecl = FD->getFriendDecl(); + if (!ActualDecl) { + const auto *FriendTypeInfo = FD->getFriendType(); + if (!FriendTypeInfo) + continue; + ActualDecl = FriendTypeInfo->getType()->getAsCXXRecordDecl(); + + if (!ActualDecl) + continue; + F.IsClass = true; + } + + if (const auto *ActualTD = dyn_cast_or_null(ActualDecl)) { + if (isa(ActualTD->getTemplatedDecl())) + F.IsClass = true; + F.Template.emplace(); + for (const auto *Param : ActualTD->getTemplateParameters()->asArray()) + F.Template->Params.emplace_back( + getSourceCode(Param, Param->getSourceRange())); + ActualDecl = ActualTD->getTemplatedDecl(); + } + + if (auto *FuncDecl = dyn_cast_or_null(ActualDecl)) { + FunctionInfo TempInfo; + parseParameters(TempInfo, FuncDecl); + F.Params.emplace(); + F.Params = std::move(TempInfo.Params); + F.ReturnType = getTypeInfoForType(FuncDecl->getReturnType(), + FuncDecl->getLangOpts()); + } + + F.Ref = + Reference(getUSRForDecl(ActualDecl), ActualDecl->getNameAsString(), + InfoType::IT_default, ActualDecl->getQualifiedNameAsString(), + getInfoRelativePath(ActualDecl)); + + RI.Friends.push_back(std::move(F)); + } +} + std::pair, std::unique_ptr> emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc, bool PublicOnly) { @@ -899,10 +1023,13 @@ emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc, // TODO: remove first call to parseBases, that function should be deleted parseBases(*RI, C); parseBases(*RI, C, /*IsFileInRootDir=*/true, PublicOnly, /*IsParent=*/true); + parseFriends(*RI, C); } 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)) { @@ -1074,6 +1201,50 @@ 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))}; +} + +std::pair, std::unique_ptr> +emitInfo(const VarDecl *D, const FullComment *FC, const Location &Loc, + bool PublicOnly) { + VarInfo Var; + bool IsInAnonymousNamespace = false; + populateSymbolInfo(Var, D, FC, Loc, IsInAnonymousNamespace); + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + if (D->getStorageClass() == StorageClass::SC_Static) + Var.IsStatic = true; + Var.Type = + getTypeInfoForType(D->getType(), D->getASTContext().getPrintingPolicy()); + + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + return {nullptr, makeAndInsertIntoParent(std::move(Var))}; +} + } // 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 7e6cbb70721ec..06c4d64c51494 100644 --- a/clang-tools-extra/clang-doc/Serialize.h +++ b/clang-tools-extra/clang-doc/Serialize.h @@ -68,6 +68,14 @@ 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); + +std::pair, std::unique_ptr> +emitInfo(const VarDecl *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 897b5d5ae4c98..eeccdd804b669 100644 --- a/clang-tools-extra/clang-doc/YAMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/YAMLGenerator.cpp @@ -408,6 +408,10 @@ llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, case InfoType::IT_typedef: InfoYAML << *static_cast(I); break; + case InfoType::IT_concept: + case InfoType::IT_variable: + case InfoType::IT_friend: + break; case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected InfoType"); diff --git a/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp b/clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp index 3b1cd18d80346..ada9122b587ae 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. @@ -345,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/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp index a71813a103df3..c35f0b941c600 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -358,8 +358,27 @@ getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix) { } // namespace clang::tidy +void ClangTidyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts, + const Preprocessor *PP) { + DiagnosticConsumer::BeginSourceFile(LangOpts, PP); + + assert(!InSourceFile); + InSourceFile = true; +} + +void ClangTidyDiagnosticConsumer::EndSourceFile() { + assert(InSourceFile); + InSourceFile = false; + + DiagnosticConsumer::EndSourceFile(); +} + void ClangTidyDiagnosticConsumer::HandleDiagnostic( DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { + // A diagnostic should not be reported outside of a + // BeginSourceFile()/EndSourceFile() pair if it has a source location. + assert(InSourceFile || Info.getLocation().isInvalid()); + if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) return; diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h index a8851e794f24b..6e7cb7bb10e57 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -292,6 +292,11 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override; + void BeginSourceFile(const LangOptions &LangOpts, + const Preprocessor *PP = nullptr) override; + + void EndSourceFile() override; + // Retrieve the diagnostics that were captured. std::vector take(); @@ -326,6 +331,11 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { bool LastErrorRelatesToUserCode = false; bool LastErrorPassesLineFilter = false; bool LastErrorWasIgnored = false; + /// Tracks whether we're currently inside a + /// `BeginSourceFile()/EndSourceFile()` pair. Outside of a source file, we + /// should only receive diagnostics that have to source location, such as + /// command-line warnings. + bool InSourceFile = false; }; } // end namespace tidy diff --git a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp index 9eeba867f5211..88d2f2c388d07 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp @@ -72,7 +72,9 @@ SizeofExpressionCheck::SizeofExpressionCheck(StringRef Name, Options.get("WarnOnSizeOfPointerToAggregate", true)), WarnOnSizeOfPointer(Options.get("WarnOnSizeOfPointer", false)), WarnOnOffsetDividedBySizeOf( - Options.get("WarnOnOffsetDividedBySizeOf", true)) {} + Options.get("WarnOnOffsetDividedBySizeOf", true)), + WarnOnSizeOfInLoopTermination( + Options.get("WarnOnSizeOfInLoopTermination", true)) {} void SizeofExpressionCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "WarnOnSizeOfConstant", WarnOnSizeOfConstant); @@ -86,6 +88,8 @@ void SizeofExpressionCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "WarnOnSizeOfPointer", WarnOnSizeOfPointer); Options.store(Opts, "WarnOnOffsetDividedBySizeOf", WarnOnOffsetDividedBySizeOf); + Options.store(Opts, "WarnOnSizeOfInLoopTermination", + WarnOnSizeOfInLoopTermination); } void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { @@ -93,6 +97,13 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { // Some of the checks should not match in template code to avoid false // positives if sizeof is applied on template argument. + auto LoopCondExpr = + [](const ast_matchers::internal::Matcher &InnerMatcher) { + return stmt(anyOf(forStmt(hasCondition(InnerMatcher)), + whileStmt(hasCondition(InnerMatcher)), + doStmt(hasCondition(InnerMatcher)))); + }; + const auto IntegerExpr = ignoringParenImpCasts(integerLiteral()); const auto ConstantExpr = ignoringParenImpCasts( anyOf(integerLiteral(), unaryOperator(hasUnaryOperand(IntegerExpr)), @@ -130,6 +141,14 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { this); } + if (WarnOnSizeOfInLoopTermination) { + auto CondExpr = binaryOperator( + allOf(has(SizeOfExpr.bind("sizeof-expr")), isComparisonOperator())); + Finder->addMatcher(LoopCondExpr(anyOf(CondExpr, hasDescendant(CondExpr))) + .bind("loop-expr"), + this); + } + // Detect sizeof(kPtr) where kPtr is 'const char* kPtr = "abc"'; const auto CharPtrType = pointerType(pointee(isAnyCharacter())); const auto ConstStrLiteralDecl = @@ -349,6 +368,23 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { diag(E->getBeginLoc(), "suspicious usage of 'sizeof(char*)'; do you mean 'strlen'?") << E->getSourceRange(); + } else if (Result.Nodes.getNodeAs("loop-expr")) { + auto *SizeofArgTy = Result.Nodes.getNodeAs("sizeof-arg-type"); + if (const auto member = dyn_cast(SizeofArgTy)) + SizeofArgTy = member->getPointeeType().getTypePtr(); + + const auto *SzOfExpr = Result.Nodes.getNodeAs("sizeof-expr"); + + if (const auto type = dyn_cast(SizeofArgTy)) { + // check if the array element size is larger than one. If true, + // the size of the array is higher than the number of elements + CharUnits sSize = Ctx.getTypeSizeInChars(type->getElementType()); + if (!sSize.isOne()) { + diag(SzOfExpr->getBeginLoc(), + "suspicious usage of 'sizeof' in the loop") + << SzOfExpr->getSourceRange(); + } + } } else if (const auto *E = Result.Nodes.getNodeAs("sizeof-pointer")) { diag(E->getBeginLoc(), "suspicious usage of 'sizeof()' on an expression " "of pointer type") diff --git a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.h b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.h index fbd62cb80fb2d..e979b4723cf2e 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.h @@ -32,6 +32,7 @@ class SizeofExpressionCheck : public ClangTidyCheck { const bool WarnOnSizeOfPointerToAggregate; const bool WarnOnSizeOfPointer; const bool WarnOnOffsetDividedBySizeOf; + const bool WarnOnSizeOfInLoopTermination; }; } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp index 8ffa44d41fa98..b14587ad7db83 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp @@ -17,8 +17,20 @@ namespace { AST_MATCHER(GotoStmt, isForwardJumping) { return Node.getBeginLoc() < Node.getLabel()->getBeginLoc(); } + +AST_MATCHER(GotoStmt, isInMacro) { + return Node.getBeginLoc().isMacroID() && Node.getEndLoc().isMacroID(); +} } // namespace +AvoidGotoCheck::AvoidGotoCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.get("IgnoreMacros", false)) {} + +void AvoidGotoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + void AvoidGotoCheck::registerMatchers(MatchFinder *Finder) { // TODO: This check does not recognize `IndirectGotoStmt` which is a // GNU extension. These must be matched separately and an AST matcher @@ -29,7 +41,10 @@ void AvoidGotoCheck::registerMatchers(MatchFinder *Finder) { auto Loop = mapAnyOf(forStmt, cxxForRangeStmt, whileStmt, doStmt); auto NestedLoop = Loop.with(hasAncestor(Loop)); - Finder->addMatcher(gotoStmt(anyOf(unless(hasAncestor(NestedLoop)), + const ast_matchers::internal::Matcher Anything = anything(); + + Finder->addMatcher(gotoStmt(IgnoreMacros ? unless(isInMacro()) : Anything, + anyOf(unless(hasAncestor(NestedLoop)), unless(isForwardJumping()))) .bind("goto"), this); diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.h index 883ba78855e7c..8eae409462c91 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/AvoidGotoCheck.h @@ -20,13 +20,16 @@ namespace clang::tidy::cppcoreguidelines { /// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/avoid-goto.html class AvoidGotoCheck : public ClangTidyCheck { public: - AvoidGotoCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + AvoidGotoCheck(StringRef Name, ClangTidyContext *Context); bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus; } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool IgnoreMacros; }; } // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt index b023f76a25432..2fb4d7f1d7349 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 4dd9b0904f075..4b3b7bf963fdc 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( diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp index cf299609e646d..268b51f76a2c3 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp @@ -120,7 +120,7 @@ void MissingStdForwardCheck::registerMatchers(MatchFinder *Finder) { equalsBoundNode("param"), equalsBoundNode("var")))))), CapturedInLambda)), callee(unresolvedLookupExpr(hasAnyDeclaration( - namedDecl(hasUnderlyingDecl(hasName("::std::forward")))))), + namedDecl(hasUnderlyingDecl(hasName(ForwardFunction)))))), unless(anyOf(hasAncestor(typeLoc()), hasAncestor(expr(hasUnevaluatedContext()))))); @@ -149,4 +149,13 @@ void MissingStdForwardCheck::check(const MatchFinder::MatchResult &Result) { << Param; } +MissingStdForwardCheck::MissingStdForwardCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ForwardFunction(Options.get("ForwardFunction", "::std::forward")) {} + +void MissingStdForwardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ForwardFunction", ForwardFunction); +} + } // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.h index 5995ad588855e..f833b8031f8af 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.h @@ -21,8 +21,7 @@ namespace clang::tidy::cppcoreguidelines { /// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/missing-std-forward.html class MissingStdForwardCheck : public ClangTidyCheck { public: - MissingStdForwardCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + MissingStdForwardCheck(StringRef Name, ClangTidyContext *Context); void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { @@ -31,6 +30,10 @@ class MissingStdForwardCheck : public ClangTidyCheck { std::optional getCheckTraversalKind() const override { return TK_IgnoreUnlessSpelledInSource; } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const StringRef ForwardFunction; }; } // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp index a1494a095f5b6..9ac7b9e057e35 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp @@ -15,16 +15,13 @@ using namespace clang::ast_matchers; namespace clang::tidy::cppcoreguidelines { void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { - if (!getLangOpts().CPlusPlus) - return; - const auto AllPointerTypes = - anyOf(hasType(pointerType()), + anyOf(hasType(hasUnqualifiedDesugaredType(pointerType())), hasType(autoType( hasDeducedType(hasUnqualifiedDesugaredType(pointerType())))), hasType(decltypeType(hasUnderlyingType(pointerType())))); - // Flag all operators +, -, +=, -=, ++, -- that result in a pointer + // Flag all operators +, -, +=, -= that result in a pointer Finder->addMatcher( binaryOperator( hasAnyOperatorName("+", "-", "+=", "-="), AllPointerTypes, @@ -32,8 +29,12 @@ void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { .bind("expr"), this); + // Flag all operators ++, -- that result in a pointer Finder->addMatcher( - unaryOperator(hasAnyOperatorName("++", "--"), hasType(pointerType())) + unaryOperator(hasAnyOperatorName("++", "--"), + hasType(hasUnqualifiedDesugaredType(pointerType())), + unless(hasUnaryOperand( + ignoringImpCasts(declRefExpr(to(isImplicit())))))) .bind("expr"), this); @@ -42,7 +43,8 @@ void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { arraySubscriptExpr( hasBase(ignoringImpCasts( anyOf(AllPointerTypes, - hasType(decayedType(hasDecayedType(pointerType()))))))) + hasType(decayedType(hasDecayedType(pointerType())))))), + hasIndex(hasType(isInteger()))) .bind("expr"), this); } diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h index 67f7d1bf9dd69..3466c72a769e9 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h @@ -23,6 +23,9 @@ class ProBoundsPointerArithmeticCheck : public ClangTidyCheck { public: ProBoundsPointerArithmeticCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.cpp index 8c386d5bc7945..272152644d7dd 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.cpp @@ -42,9 +42,9 @@ void RvalueReferenceParamNotMovedCheck::registerMatchers(MatchFinder *Finder) { StatementMatcher MoveCallMatcher = callExpr( argumentCountIs(1), - anyOf(callee(functionDecl(hasName("::std::move"))), + anyOf(callee(functionDecl(hasName(MoveFunction))), callee(unresolvedLookupExpr(hasAnyDeclaration( - namedDecl(hasUnderlyingDecl(hasName("::std::move"))))))), + namedDecl(hasUnderlyingDecl(hasName(MoveFunction))))))), hasArgument( 0, argumentOf( AllowPartialMove, @@ -122,7 +122,8 @@ RvalueReferenceParamNotMovedCheck::RvalueReferenceParamNotMovedCheck( AllowPartialMove(Options.get("AllowPartialMove", false)), IgnoreUnnamedParams(Options.get("IgnoreUnnamedParams", false)), IgnoreNonDeducedTemplateTypes( - Options.get("IgnoreNonDeducedTemplateTypes", false)) {} + Options.get("IgnoreNonDeducedTemplateTypes", false)), + MoveFunction(Options.get("MoveFunction", "::std::move")) {} void RvalueReferenceParamNotMovedCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { @@ -130,6 +131,7 @@ void RvalueReferenceParamNotMovedCheck::storeOptions( Options.store(Opts, "IgnoreUnnamedParams", IgnoreUnnamedParams); Options.store(Opts, "IgnoreNonDeducedTemplateTypes", IgnoreNonDeducedTemplateTypes); + Options.store(Opts, "MoveFunction", MoveFunction); } } // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.h index d8c3d2bd4ba0e..950c0206745d7 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.h @@ -32,6 +32,7 @@ class RvalueReferenceParamNotMovedCheck : public ClangTidyCheck { const bool AllowPartialMove; const bool IgnoreUnnamedParams; const bool IgnoreNonDeducedTemplateTypes; + const StringRef MoveFunction; }; } // namespace clang::tidy::cppcoreguidelines diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp index 0de143dbb1b89..0b6b8d9c97135 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp @@ -18,6 +18,12 @@ using namespace clang::ast_matchers; namespace clang::tidy::cppcoreguidelines { +namespace { +AST_MATCHER(CXXRecordDecl, isInMacro) { + return Node.getBeginLoc().isMacroID() && Node.getEndLoc().isMacroID(); +} +} // namespace + SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), AllowMissingMoveFunctions(Options.get( @@ -26,7 +32,8 @@ SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck( AllowMissingMoveFunctionsWhenCopyIsDeleted( Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)), AllowImplicitlyDeletedCopyOrMove( - Options.get("AllowImplicitlyDeletedCopyOrMove", false)) {} + Options.get("AllowImplicitlyDeletedCopyOrMove", false)), + IgnoreMacros(Options.get("IgnoreMacros", true)) {} void SpecialMemberFunctionsCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { @@ -36,6 +43,7 @@ void SpecialMemberFunctionsCheck::storeOptions( AllowMissingMoveFunctionsWhenCopyIsDeleted); Options.store(Opts, "AllowImplicitlyDeletedCopyOrMove", AllowImplicitlyDeletedCopyOrMove); + Options.store(Opts, "IgnoreMacros", IgnoreMacros); } std::optional @@ -45,11 +53,12 @@ SpecialMemberFunctionsCheck::getCheckTraversalKind() const { } void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) { - auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted()); + const auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted()); + const ast_matchers::internal::Matcher Anything = anything(); Finder->addMatcher( cxxRecordDecl( - unless(isImplicit()), + unless(isImplicit()), IgnoreMacros ? unless(isInMacro()) : Anything, eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")), has(cxxConstructorDecl(isCopyConstructor(), IsNotImplicitOrDeleted) diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h index dee01cb5a9fdd..c18ed7db055ba 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h @@ -69,6 +69,7 @@ class SpecialMemberFunctionsCheck : public ClangTidyCheck { const bool AllowMissingMoveFunctionsWhenCopyIsDeleted; const bool AllowImplicitlyDeletedCopyOrMove; ClassDefiningSpecialMembersMap ClassWithSpecialMembers; + const bool IgnoreMacros; }; } // namespace clang::tidy::cppcoreguidelines 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 0000000000000..ec7d9237afa3c --- /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 0000000000000..dfa4b7e3fda62 --- /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/misc/ConfusableIdentifierCheck.cpp b/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp index 7639476980529..79ae5ee98182b 100644 --- a/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp @@ -1,5 +1,4 @@ -//===--- ConfusableIdentifierCheck.cpp - -// clang-tidy--------------------------===// +//===--- ConfusableIdentifierCheck.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. @@ -9,6 +8,7 @@ #include "ConfusableIdentifierCheck.h" +#include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/ConvertUTF.h" @@ -88,99 +88,98 @@ static llvm::SmallString<64U> skeleton(StringRef Name) { return Skeleton; } -static bool mayShadowImpl(const DeclContext *DC0, const DeclContext *DC1) { - return DC0 && DC0 == DC1; -} - -static bool mayShadowImpl(const NamedDecl *ND0, const NamedDecl *ND1) { - return isa(ND0) || isa(ND1); -} - -static bool isMemberOf(const ConfusableIdentifierCheck::ContextInfo *DC0, - const ConfusableIdentifierCheck::ContextInfo *DC1) { - return llvm::is_contained(DC1->Bases, DC0->PrimaryContext); -} - -static bool enclosesContext(const ConfusableIdentifierCheck::ContextInfo *DC0, - const ConfusableIdentifierCheck::ContextInfo *DC1) { - if (DC0->PrimaryContext == DC1->PrimaryContext) - return true; - - return llvm::is_contained(DC0->PrimaryContexts, DC1->PrimaryContext) || - llvm::is_contained(DC1->PrimaryContexts, DC0->PrimaryContext); -} - -static bool mayShadow(const NamedDecl *ND0, - const ConfusableIdentifierCheck::ContextInfo *DC0, - const NamedDecl *ND1, - const ConfusableIdentifierCheck::ContextInfo *DC1) { - - if (!DC0->Bases.empty() && !DC1->Bases.empty()) { - // if any of the declaration is a non-private member of the other - // declaration, it's shadowed by the former - - if (ND1->getAccess() != AS_private && isMemberOf(DC1, DC0)) - return true; +namespace { +struct Entry { + const NamedDecl *ND; + const Decl *Parent; + bool FromDerivedClass; +}; +} // namespace - if (ND0->getAccess() != AS_private && isMemberOf(DC0, DC1)) +// Map from a context to the declarations in that context with the current +// skeleton. At most one entry per distinct identifier is tracked. The +// context is usually a `DeclContext`, but can also be a template declaration +// that has no corresponding context, such as an alias template or variable +// template. +using DeclsWithinContextMap = + llvm::DenseMap>; + +static bool addToContext(DeclsWithinContextMap &DeclsWithinContext, + const Decl *Context, Entry E) { + auto &Decls = DeclsWithinContext[Context]; + if (!Decls.empty() && + Decls.back().ND->getIdentifier() == E.ND->getIdentifier()) { + // Already have a declaration with this identifier in this context. Don't + // track another one. This means that if an outer name is confusable with an + // inner name, we'll only diagnose the outer name once, pointing at the + // first inner declaration with that name. + if (Decls.back().FromDerivedClass && !E.FromDerivedClass) { + // Prefer the declaration that's not from the derived class, because that + // conflicts with more declarations. + Decls.back() = E; return true; - } - - if (!mayShadowImpl(DC0->NonTransparentContext, DC1->NonTransparentContext) && - !mayShadowImpl(ND0, ND1)) + } return false; - - return enclosesContext(DC0, DC1); + } + Decls.push_back(E); + return true; } -const ConfusableIdentifierCheck::ContextInfo * -ConfusableIdentifierCheck::getContextInfo(const DeclContext *DC) { - const DeclContext *PrimaryContext = DC->getPrimaryContext(); - auto [It, Inserted] = ContextInfos.try_emplace(PrimaryContext); - if (!Inserted) - return &It->second; - - ContextInfo &Info = It->second; - Info.PrimaryContext = PrimaryContext; - Info.NonTransparentContext = PrimaryContext; +static void addToEnclosingContexts(DeclsWithinContextMap &DeclsWithinContext, + const Decl *Parent, const NamedDecl *ND) { + const Decl *Outer = Parent; + while (Outer) { + if (const auto *NS = dyn_cast(Outer)) + Outer = NS->getCanonicalDecl(); + + if (!addToContext(DeclsWithinContext, Outer, {ND, Parent, false})) + return; + + if (const auto *RD = dyn_cast(Outer)) { + RD = RD->getDefinition(); + if (RD) { + RD->forallBases([&](const CXXRecordDecl *Base) { + addToContext(DeclsWithinContext, Base, {ND, Parent, true}); + return true; + }); + } + } - while (Info.NonTransparentContext->isTransparentContext()) { - Info.NonTransparentContext = Info.NonTransparentContext->getParent(); - if (!Info.NonTransparentContext) + auto *OuterDC = Outer->getDeclContext(); + if (!OuterDC) break; + Outer = cast_or_null(OuterDC->getNonTransparentContext()); } +} + +void ConfusableIdentifierCheck::check( + const ast_matchers::MatchFinder::MatchResult &Result) { + const auto *ND = Result.Nodes.getNodeAs("nameddecl"); + if (!ND) + return; - if (Info.NonTransparentContext) - Info.NonTransparentContext = - Info.NonTransparentContext->getPrimaryContext(); + addDeclToCheck(ND, + cast(ND->getDeclContext()->getNonTransparentContext())); - while (DC) { - if (!isa(DC) && !isa(DC)) - Info.PrimaryContexts.push_back(DC->getPrimaryContext()); - DC = DC->getParent(); + // Associate template parameters with this declaration of this template. + if (const auto *TD = dyn_cast(ND)) { + for (const NamedDecl *Param : *TD->getTemplateParameters()) + addDeclToCheck(Param, TD->getTemplatedDecl()); } - if (const auto *RD = dyn_cast(PrimaryContext)) { - RD = RD->getDefinition(); - if (RD) { - Info.Bases.push_back(RD); - RD->forallBases([&](const CXXRecordDecl *Base) { - Info.Bases.push_back(Base); - return false; - }); - } + // Associate function parameters with this declaration of this function. + if (const auto *FD = dyn_cast(ND)) { + for (const NamedDecl *Param : FD->parameters()) + addDeclToCheck(Param, ND); } - - return &Info; } -void ConfusableIdentifierCheck::check( - const ast_matchers::MatchFinder::MatchResult &Result) { - const auto *ND = Result.Nodes.getNodeAs("nameddecl"); - if (!ND) +void ConfusableIdentifierCheck::addDeclToCheck(const NamedDecl *ND, + const Decl *Parent) { + if (!ND || !Parent) return; - IdentifierInfo *NDII = ND->getIdentifier(); + const IdentifierInfo *NDII = ND->getIdentifier(); if (!NDII) return; @@ -188,34 +187,78 @@ void ConfusableIdentifierCheck::check( if (NDName.empty()) return; - const ContextInfo *Info = getContextInfo(ND->getDeclContext()); + NameToDecls[NDII].push_back({ND, Parent}); +} + +void ConfusableIdentifierCheck::onEndOfTranslationUnit() { + llvm::StringMap> SkeletonToNames; + // Compute the skeleton for each identifier. + for (auto &[Ident, Decls] : NameToDecls) { + SkeletonToNames[skeleton(Ident->getName())].push_back(Ident); + } - llvm::SmallVector &Mapped = Mapper[skeleton(NDName)]; - for (const Entry &E : Mapped) { - if (!mayShadow(ND, Info, E.Declaration, E.Info)) + // Visit each skeleton with more than one identifier. + for (auto &[Skel, Idents] : SkeletonToNames) { + if (Idents.size() < 2) { continue; + } - const IdentifierInfo *ONDII = E.Declaration->getIdentifier(); - StringRef ONDName = ONDII->getName(); - if (ONDName == NDName) - continue; + // Find the declaration contexts that transitively contain each identifier. + DeclsWithinContextMap DeclsWithinContext; + for (const IdentifierInfo *II : Idents) { + for (auto [ND, Parent] : NameToDecls[II]) { + addToEnclosingContexts(DeclsWithinContext, Parent, ND); + } + } - diag(ND->getLocation(), "%0 is confusable with %1") << ND << E.Declaration; - diag(E.Declaration->getLocation(), "other declaration found here", - DiagnosticIDs::Note); + // Check to see if any declaration is declared in a context that + // transitively contains another declaration with a different identifier but + // the same skeleton. + for (const IdentifierInfo *II : Idents) { + for (auto [OuterND, OuterParent] : NameToDecls[II]) { + for (Entry Inner : DeclsWithinContext[OuterParent]) { + // Don't complain if the identifiers are the same. + if (OuterND->getIdentifier() == Inner.ND->getIdentifier()) + continue; + + // Don't complain about a derived-class name shadowing a base class + // private member. + if (OuterND->getAccess() == AS_private && Inner.FromDerivedClass) + continue; + + // If the declarations are in the same context, only diagnose the + // later one. + if (OuterParent == Inner.Parent && + Inner.ND->getASTContext() + .getSourceManager() + .isBeforeInTranslationUnit(Inner.ND->getLocation(), + OuterND->getLocation())) + continue; + + diag(Inner.ND->getLocation(), "%0 is confusable with %1") + << Inner.ND << OuterND; + diag(OuterND->getLocation(), "other declaration found here", + DiagnosticIDs::Note); + } + } + } } - Mapped.push_back({ND, Info}); -} - -void ConfusableIdentifierCheck::onEndOfTranslationUnit() { - Mapper.clear(); - ContextInfos.clear(); + NameToDecls.clear(); } void ConfusableIdentifierCheck::registerMatchers( ast_matchers::MatchFinder *Finder) { - Finder->addMatcher(ast_matchers::namedDecl().bind("nameddecl"), this); + // Parameter declarations sometimes use the translation unit or some outer + // enclosing context as their `DeclContext`, instead of their parent, so + // we handle them specially in `check`. + auto AnyParamDecl = ast_matchers::anyOf( + ast_matchers::parmVarDecl(), ast_matchers::templateTypeParmDecl(), + ast_matchers::nonTypeTemplateParmDecl(), + ast_matchers::templateTemplateParmDecl()); + Finder->addMatcher(ast_matchers::namedDecl(ast_matchers::unless(AnyParamDecl)) + .bind("nameddecl"), + this); } } // namespace clang::tidy::misc diff --git a/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.h b/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.h index f3b0c8ed00306..9cce6cce67682 100644 --- a/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.h +++ b/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.h @@ -1,5 +1,4 @@ -//===--- ConfusableIdentifierCheck.h - clang-tidy -//-------------------------------*- C++ -*-===// +//===--- ConfusableIdentifierCheck.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. @@ -11,7 +10,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CONFUSABLE_IDENTIFIER_CHECK_H #include "../ClangTidyCheck.h" -#include +#include "llvm/ADT/DenseMap.h" namespace clang::tidy::misc { @@ -31,23 +30,13 @@ class ConfusableIdentifierCheck : public ClangTidyCheck { return TK_IgnoreUnlessSpelledInSource; } - struct ContextInfo { - const DeclContext *PrimaryContext; - const DeclContext *NonTransparentContext; - llvm::SmallVector PrimaryContexts; - llvm::SmallVector Bases; - }; - private: - struct Entry { - const NamedDecl *Declaration; - const ContextInfo *Info; - }; - - const ContextInfo *getContextInfo(const DeclContext *DC); + void addDeclToCheck(const NamedDecl *ND, const Decl *Parent); - llvm::StringMap> Mapper; - std::unordered_map ContextInfos; + llvm::DenseMap< + const IdentifierInfo *, + llvm::SmallVector, 1>> + NameToDecls; }; } // namespace clang::tidy::misc diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index bab1167fb15ff..619a27b2f9bb6 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -42,6 +42,7 @@ add_clang_library(clangTidyModernizeModule STATIC UseNullptrCheck.cpp UseOverrideCheck.cpp UseRangesCheck.cpp + UseScopedLockCheck.cpp UseStartsEndsWithCheck.cpp UseStdFormatCheck.cpp UseStdNumbersCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index 0cf59b6e0216a..fdf38bc4b6308 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -43,6 +43,7 @@ #include "UseNullptrCheck.h" #include "UseOverrideCheck.h" #include "UseRangesCheck.h" +#include "UseScopedLockCheck.h" #include "UseStartsEndsWithCheck.h" #include "UseStdFormatCheck.h" #include "UseStdNumbersCheck.h" @@ -80,6 +81,8 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck( "modernize-use-integer-sign-comparison"); CheckFactories.registerCheck("modernize-use-ranges"); + CheckFactories.registerCheck( + "modernize-use-scoped-lock"); CheckFactories.registerCheck( "modernize-use-starts-ends-with"); CheckFactories.registerCheck("modernize-use-std-format"); diff --git a/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp new file mode 100644 index 0000000000000..9c2fc9e06fb45 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp @@ -0,0 +1,311 @@ +//===--- UseScopedLockCheck.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 "UseScopedLockCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/Twine.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +static bool isLockGuardDecl(const NamedDecl *Decl) { + return Decl->getDeclName().isIdentifier() && + Decl->getName() == "lock_guard" && Decl->isInStdNamespace(); +} + +static bool isLockGuard(const QualType &Type) { + if (const auto *Record = Type->getAs()) + if (const RecordDecl *Decl = Record->getDecl()) + return isLockGuardDecl(Decl); + + if (const auto *TemplateSpecType = Type->getAs()) + if (const TemplateDecl *Decl = + TemplateSpecType->getTemplateName().getAsTemplateDecl()) + return isLockGuardDecl(Decl); + + return false; +} + +static llvm::SmallVector +getLockGuardsFromDecl(const DeclStmt *DS) { + llvm::SmallVector LockGuards; + + for (const Decl *Decl : DS->decls()) { + if (const auto *VD = dyn_cast(Decl)) { + const QualType Type = + VD->getType().getCanonicalType().getUnqualifiedType(); + if (isLockGuard(Type)) + LockGuards.push_back(VD); + } + } + + return LockGuards; +} + +// Scans through the statements in a block and groups consecutive +// 'std::lock_guard' variable declarations together. +static llvm::SmallVector> +findLocksInCompoundStmt(const CompoundStmt *Block, + const ast_matchers::MatchFinder::MatchResult &Result) { + // store groups of consecutive 'std::lock_guard' declarations + llvm::SmallVector> LockGuardGroups; + llvm::SmallVector CurrentLockGuardGroup; + + auto AddAndClearCurrentGroup = [&]() { + if (!CurrentLockGuardGroup.empty()) { + LockGuardGroups.push_back(CurrentLockGuardGroup); + CurrentLockGuardGroup.clear(); + } + }; + + for (const Stmt *Stmt : Block->body()) { + if (const auto *DS = dyn_cast(Stmt)) { + llvm::SmallVector LockGuards = getLockGuardsFromDecl(DS); + + if (!LockGuards.empty()) { + CurrentLockGuardGroup.append(LockGuards); + continue; + } + } + AddAndClearCurrentGroup(); + } + + AddAndClearCurrentGroup(); + + return LockGuardGroups; +} + +static TemplateSpecializationTypeLoc +getTemplateLockGuardTypeLoc(const TypeSourceInfo *SourceInfo) { + const TypeLoc Loc = SourceInfo->getTypeLoc(); + + const auto ElaboratedLoc = Loc.getAs(); + if (!ElaboratedLoc) + return {}; + + return ElaboratedLoc.getNamedTypeLoc().getAs(); +} + +// Find the exact source range of the 'lock_guard' token +static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo) { + const TypeLoc LockGuardTypeLoc = SourceInfo->getTypeLoc(); + + return SourceRange(LockGuardTypeLoc.getBeginLoc(), + LockGuardTypeLoc.getEndLoc()); +} + +// Find the exact source range of the 'lock_guard' name token +static SourceRange getLockGuardNameRange(const TypeSourceInfo *SourceInfo) { + const TemplateSpecializationTypeLoc TemplateLoc = + getTemplateLockGuardTypeLoc(SourceInfo); + if (!TemplateLoc) + return {}; + + return SourceRange(TemplateLoc.getTemplateNameLoc(), + TemplateLoc.getLAngleLoc().getLocWithOffset(-1)); +} + +const static StringRef UseScopedLockMessage = + "use 'std::scoped_lock' instead of 'std::lock_guard'"; + +UseScopedLockCheck::UseScopedLockCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnSingleLocks(Options.get("WarnOnSingleLocks", true)), + WarnOnUsingAndTypedef(Options.get("WarnOnUsingAndTypedef", true)) {} + +void UseScopedLockCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnSingleLocks", WarnOnSingleLocks); + Options.store(Opts, "WarnOnUsingAndTypedef", WarnOnUsingAndTypedef); +} + +void UseScopedLockCheck::registerMatchers(MatchFinder *Finder) { + const auto LockGuardClassDecl = + namedDecl(hasName("lock_guard"), isInStdNamespace()); + + const auto LockGuardType = qualType(anyOf( + hasUnqualifiedDesugaredType( + recordType(hasDeclaration(LockGuardClassDecl))), + elaboratedType(namesType(hasUnqualifiedDesugaredType( + templateSpecializationType(hasDeclaration(LockGuardClassDecl))))))); + + const auto LockVarDecl = varDecl(hasType(LockGuardType)); + + if (WarnOnSingleLocks) { + Finder->addMatcher( + compoundStmt( + unless(isExpansionInSystemHeader()), + has(declStmt(has(LockVarDecl)).bind("lock-decl-single")), + unless(has(declStmt(unless(equalsBoundNode("lock-decl-single")), + has(LockVarDecl))))), + this); + } + + Finder->addMatcher( + compoundStmt(unless(isExpansionInSystemHeader()), + has(declStmt(has(LockVarDecl)).bind("lock-decl-multiple")), + has(declStmt(unless(equalsBoundNode("lock-decl-multiple")), + has(LockVarDecl)))) + .bind("block-multiple"), + this); + + if (WarnOnUsingAndTypedef) { + // Match 'typedef std::lock_guard Lock' + Finder->addMatcher(typedefDecl(unless(isExpansionInSystemHeader()), + hasUnderlyingType(LockGuardType)) + .bind("lock-guard-typedef"), + this); + + // Match 'using Lock = std::lock_guard' + Finder->addMatcher( + typeAliasDecl( + unless(isExpansionInSystemHeader()), + hasType(elaboratedType(namesType(templateSpecializationType( + hasDeclaration(LockGuardClassDecl)))))) + .bind("lock-guard-using-alias"), + this); + + // Match 'using std::lock_guard' + Finder->addMatcher( + usingDecl(unless(isExpansionInSystemHeader()), + hasAnyUsingShadowDecl(hasTargetDecl(LockGuardClassDecl))) + .bind("lock-guard-using-decl"), + this); + } +} + +void UseScopedLockCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *DS = Result.Nodes.getNodeAs("lock-decl-single")) { + llvm::SmallVector Decls = getLockGuardsFromDecl(DS); + diagOnMultipleLocks({Decls}, Result); + return; + } + + if (const auto *Compound = + Result.Nodes.getNodeAs("block-multiple")) { + diagOnMultipleLocks(findLocksInCompoundStmt(Compound, Result), Result); + return; + } + + if (const auto *Typedef = + Result.Nodes.getNodeAs("lock-guard-typedef")) { + diagOnSourceInfo(Typedef->getTypeSourceInfo(), Result); + return; + } + + if (const auto *UsingAlias = + Result.Nodes.getNodeAs("lock-guard-using-alias")) { + diagOnSourceInfo(UsingAlias->getTypeSourceInfo(), Result); + return; + } + + if (const auto *Using = + Result.Nodes.getNodeAs("lock-guard-using-decl")) { + diagOnUsingDecl(Using, Result); + } +} + +void UseScopedLockCheck::diagOnSingleLock( + const VarDecl *LockGuard, const MatchFinder::MatchResult &Result) { + auto Diag = diag(LockGuard->getBeginLoc(), UseScopedLockMessage); + + const SourceRange LockGuardTypeRange = + getLockGuardRange(LockGuard->getTypeSourceInfo()); + + if (LockGuardTypeRange.isInvalid()) + return; + + // Create Fix-its only if we can find the constructor call to properly handle + // 'std::lock_guard l(m, std::adopt_lock)' case. + const auto *CtorCall = dyn_cast(LockGuard->getInit()); + if (!CtorCall) + return; + + if (CtorCall->getNumArgs() == 1) { + Diag << FixItHint::CreateReplacement(LockGuardTypeRange, + "std::scoped_lock"); + return; + } + + if (CtorCall->getNumArgs() == 2) { + const Expr *const *CtorArgs = CtorCall->getArgs(); + + const Expr *MutexArg = CtorArgs[0]; + const Expr *AdoptLockArg = CtorArgs[1]; + + const StringRef MutexSourceText = Lexer::getSourceText( + CharSourceRange::getTokenRange(MutexArg->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + const StringRef AdoptLockSourceText = Lexer::getSourceText( + CharSourceRange::getTokenRange(AdoptLockArg->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + + Diag << FixItHint::CreateReplacement(LockGuardTypeRange, "std::scoped_lock") + << FixItHint::CreateReplacement( + SourceRange(MutexArg->getBeginLoc(), AdoptLockArg->getEndLoc()), + (llvm::Twine(AdoptLockSourceText) + ", " + MutexSourceText) + .str()); + return; + } + + llvm_unreachable("Invalid argument number of std::lock_guard constructor"); +} + +void UseScopedLockCheck::diagOnMultipleLocks( + const llvm::SmallVector> &LockGroups, + const ast_matchers::MatchFinder::MatchResult &Result) { + for (const llvm::SmallVector &Group : LockGroups) { + if (Group.size() == 1) { + if (WarnOnSingleLocks) + diagOnSingleLock(Group[0], Result); + } else { + diag(Group[0]->getBeginLoc(), + "use single 'std::scoped_lock' instead of multiple " + "'std::lock_guard'"); + + for (const VarDecl *Lock : llvm::drop_begin(Group)) + diag(Lock->getLocation(), "additional 'std::lock_guard' declared here", + DiagnosticIDs::Note); + } + } +} + +void UseScopedLockCheck::diagOnSourceInfo( + const TypeSourceInfo *LockGuardSourceInfo, + const ast_matchers::MatchFinder::MatchResult &Result) { + const TypeLoc TL = LockGuardSourceInfo->getTypeLoc(); + + if (const auto ElaboratedTL = TL.getAs()) { + auto Diag = diag(ElaboratedTL.getBeginLoc(), UseScopedLockMessage); + + const SourceRange LockGuardRange = + getLockGuardNameRange(LockGuardSourceInfo); + if (LockGuardRange.isInvalid()) + return; + + Diag << FixItHint::CreateReplacement(LockGuardRange, "scoped_lock"); + } +} + +void UseScopedLockCheck::diagOnUsingDecl( + const UsingDecl *UsingDecl, + const ast_matchers::MatchFinder::MatchResult &Result) { + diag(UsingDecl->getLocation(), UseScopedLockMessage) + << FixItHint::CreateReplacement(UsingDecl->getLocation(), "scoped_lock"); +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.h b/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.h new file mode 100644 index 0000000000000..a5697805c15ca --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.h @@ -0,0 +1,54 @@ +//===--- UseScopedLockCheck.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_MODERNIZE_USESCOPEDLOCKCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESCOPEDLOCKCHECK_H + +#include "../ClangTidyCheck.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Stmt.h" +#include + +namespace clang::tidy::modernize { + +/// Finds uses of ``std::lock_guard`` and suggests replacing them with C++17's +/// alternative ``std::scoped_lock``. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-scoped-lock.html +class UseScopedLockCheck : public ClangTidyCheck { +public: + UseScopedLockCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus17; + } + std::optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + void diagOnSingleLock(const VarDecl *LockGuard, + const ast_matchers::MatchFinder::MatchResult &Result); + void diagOnMultipleLocks( + const llvm::SmallVector> &LockGroups, + const ast_matchers::MatchFinder::MatchResult &Result); + void diagOnSourceInfo(const TypeSourceInfo *LockGuardSourceInfo, + const ast_matchers::MatchFinder::MatchResult &Result); + void diagOnUsingDecl(const UsingDecl *UsingDecl, + const ast_matchers::MatchFinder::MatchResult &Result); + + const bool WarnOnSingleLocks; + const bool WarnOnUsingAndTypedef; +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESCOPEDLOCKCHECK_H diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp index a877f9a7ee912..d89c3a69fc841 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 8bfd814d16357..b52043416e769 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/readability/FunctionSizeCheck.cpp b/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp index 3313bcb39b7f3..8e3a2e306dbf7 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 106c69ff07393..f668ab18fea52 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/RedundantInlineSpecifierCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp index 969bf2d1add6b..7f1882c775c59 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp @@ -72,11 +72,13 @@ static SourceLocation getInlineTokenLocation(SourceRange RangeLocation, } void RedundantInlineSpecifierCheck::registerMatchers(MatchFinder *Finder) { + const auto IsPartOfRecordDecl = hasAncestor(recordDecl()); Finder->addMatcher( functionDecl(isInlineSpecified(), - anyOf(isConstexpr(), isDeleted(), isDefaulted(), + anyOf(isConstexpr(), isDeleted(), + allOf(isDefaulted(), IsPartOfRecordDecl), isInternalLinkage(StrictMode), - allOf(isDefinition(), hasAncestor(recordDecl())))) + allOf(isDefinition(), IsPartOfRecordDecl))) .bind("fun_decl"), this); @@ -88,7 +90,6 @@ void RedundantInlineSpecifierCheck::registerMatchers(MatchFinder *Finder) { this); if (getLangOpts().CPlusPlus17) { - const auto IsPartOfRecordDecl = hasAncestor(recordDecl()); Finder->addMatcher( varDecl( isInlineSpecified(), diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index 3b991e5e9013f..e274236527817 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -440,7 +440,7 @@ QualType declaredType(const TypeDecl *D) { if (const auto *Args = CTSD->getTemplateArgsAsWritten()) return Context.getTemplateSpecializationType( TemplateName(CTSD->getSpecializedTemplate()), Args->arguments(), - /*CanonicalArgs=*/std::nullopt); + /*CanonicalArgs=*/{}); return Context.getTypeDeclType(D); } diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 29321f7cd3fa2..a703009e2b467 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/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index c9e8a175b5d76..2c858e28fa243 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 8a7809d6677ee..3a6bf155ee153 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/tweaks/DefineInline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp index d6556bba14725..c9704492bf1cd 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp @@ -341,7 +341,7 @@ const FunctionDecl *findTarget(const FunctionDecl *FD) { // Returns the beginning location for a FunctionDecl. Returns location of // template keyword for templated functions. -const SourceLocation getBeginLoc(const FunctionDecl *FD) { +SourceLocation getBeginLoc(const FunctionDecl *FD) { // Include template parameter list. if (auto *FTD = FD->getDescribedFunctionTemplate()) return FTD->getBeginLoc(); diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp index 90dac3b76c648..c74250ccbe9ea 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp @@ -359,9 +359,9 @@ struct ParsedBinaryOperator { // + c <- End // / \ // a b <- Start -const SourceRange getBinaryOperatorRange(const SelectionTree::Node &N, - const SourceManager &SM, - const LangOptions &LangOpts) { +SourceRange getBinaryOperatorRange(const SelectionTree::Node &N, + const SourceManager &SM, + const LangOptions &LangOpts) { // If N is not a suitable binary operator, bail out. ParsedBinaryOperator Op; if (!Op.parse(N.ignoreImplicit()) || !Op.associative() || diff --git a/clang-tools-extra/clangd/test/positionencoding.test b/clang-tools-extra/clangd/test/positionencoding.test new file mode 100644 index 0000000000000..eea7a1a596e9a --- /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 b11d194da04db..f287439f10cab 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -668,7 +668,6 @@ class FlagsConfigProvider : public config::Provider { std::optional IndexSpec; std::optional BGPolicy; std::optional ArgumentLists; - std::optional HeaderInsertionPolicy; // If --compile-commands-dir arg was invoked, check value and override // default path. @@ -713,11 +712,6 @@ class FlagsConfigProvider : public config::Provider { BGPolicy = Config::BackgroundPolicy::Skip; } - // If CLI has set never, use that regardless of what the config files have - if (HeaderInsertion == Config::HeaderInsertionPolicy::NeverInsert) { - HeaderInsertionPolicy = Config::HeaderInsertionPolicy::NeverInsert; - } - if (std::optional Enable = shouldEnableFunctionArgSnippets()) { ArgumentLists = *Enable ? Config::ArgumentListsPolicy::FullPlaceholders : Config::ArgumentListsPolicy::Delimiters; @@ -732,8 +726,8 @@ class FlagsConfigProvider : public config::Provider { C.Index.Background = *BGPolicy; if (ArgumentLists) C.Completion.ArgumentLists = *ArgumentLists; - if (HeaderInsertionPolicy) - C.Completion.HeaderInsertion = *HeaderInsertionPolicy; + if (HeaderInsertion.getNumOccurrences()) + C.Completion.HeaderInsertion = HeaderInsertion; if (AllScopesCompletion.getNumOccurrences()) C.Completion.AllScopes = AllScopesCompletion; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 311f0d98904ad..b7c64c7a06745 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -4363,6 +4363,36 @@ TEST(CompletionTest, PreambleFromDifferentTarget) { EXPECT_THAT(Result.Completions, Not(testing::IsEmpty())); EXPECT_THAT(Signatures.signatures, Not(testing::IsEmpty())); } + +TEST(CompletionTest, SkipExplicitObjectParameter) { + Annotations Code(R"cpp( + struct A { + void foo(this auto&& self, int arg); + }; + + int main() { + A a {}; + a.^ + } + )cpp"); + + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs = {"-std=c++23"}; + + auto Preamble = TU.preamble(); + ASSERT_TRUE(Preamble); + + CodeCompleteOptions Opts{}; + + MockFS FS; + auto Inputs = TU.inputs(FS); + auto Result = codeComplete(testPath(TU.Filename), Code.point(), + Preamble.get(), Inputs, Opts); + + EXPECT_THAT(Result.Completions, + ElementsAre(AllOf(named("foo"), signature("(int arg)"), + snippetSuffix("(${1:int arg})")))); +} } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 19ccd1790e757..f8f183e9de1cc 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -136,6 +136,18 @@ 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:`modernize-use-scoped-lock + ` check. + + Finds uses of ``std::lock_guard`` and suggests replacing them with C++17's + alternative ``std::scoped_lock``. + - New :doc:`portability-avoid-pragma-once ` check. @@ -167,6 +179,11 @@ Changes in existing checks ` check to detect conversion in argument of ``std::make_optional``. +- Improved :doc: `bugprone-sizeof-expression + ` check by adding + `WarnOnSizeOfInLoopTermination` option to detect misuses of ``sizeof`` + expression in loop conditions. + - Improved :doc:`bugprone-string-constructor ` check to find suspicious calls of ``std::string`` constructor with char pointer, start position and @@ -191,11 +208,42 @@ Changes in existing checks ` check by fixing a false positive where ``strerror`` was flagged as MT-unsafe. +- Improved :doc:`cppcoreguidelines-avoid-goto + ` check by adding the option + `IgnoreMacros` to ignore ``goto`` labels defined in macros. + +- Improved :doc:`cppcoreguidelines-missing-std-forward + ` check by adding a + flag to specify the function used for forwarding instead of ``std::forward``. + +- Improved :doc:`cppcoreguidelines-pro-bounds-pointer-arithmetic + ` check by + fixing false positives when calling indexing operators that do not perform + pointer arithmetic in template, for example ``std::map::operator[]`` and + when pointer arithmetic was used through type aliases. + +- Improved :doc:`cppcoreguidelines-rvalue-reference-param-not-moved + ` check + by adding a flag to specify the function used for moving instead of + ``std::move``. + +- Improved :doc:`cppcoreguidelines-special-member-functions + ` check by + adding the option `IgnoreMacros` to ignore classes defined in macros. + - Improved :doc:`google-readability-namespace-comments ` check by adding the option `AllowOmittingNamespaceComments` to accept if a namespace comment is omitted entirely. +- Improved :doc:`hicpp-avoid-goto + ` check by adding the option + `IgnoreMacros` to ignore ``goto`` labels defined in macros. + +- Improved :doc:`hicpp-special-member-functions + ` check by adding the + option `IgnoreMacros` to ignore classes defined in macros. + - Improved :doc:`llvm-namespace-comment ` check by adding the option `AllowOmittingNamespaceComments` to accept if a namespace comment is omitted @@ -265,11 +313,18 @@ 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 @@ -279,6 +334,10 @@ Changes in existing checks ` check by adding the option `AllowedTypes`, that excludes specified types from adding qualifiers. +- Improved :doc:`readability-redundant-inline-specifier + ` check by fixing + false positives on out-of-line explicitly defaulted functions. + - Improved :doc:`readability-redundant-smartptr-get ` check by fixing some false positives involving smart pointers to arrays. diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/sizeof-expression.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/sizeof-expression.rst index 29edb26ed7aa2..04824cc1fe0e4 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/sizeof-expression.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/sizeof-expression.rst @@ -316,3 +316,12 @@ Options When `true`, the check will warn on pointer arithmetic where the element count is obtained from a division with ``sizeof(...)``, e.g., ``Ptr + Bytes / sizeof(*T)``. Default is `true`. + +.. option:: WarnOnSizeOfInLoopTermination + + When `true`, the check will warn about incorrect use of sizeof expression + in loop termination condition. The warning triggers if the ``sizeof`` + expression appears to be incorrectly used to determine the number of + array/buffer elements. + e.g, ``long arr[10]; for(int i = 0; i < sizeof(arr); i++) { ... }``. Default + is `true`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-goto.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-goto.rst index 71b579a4ae99e..1f9dc0a1edb3a 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-goto.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/avoid-goto.rst @@ -50,3 +50,12 @@ Modern C++ needs ``goto`` only to jump out of nested loops. some_operation(); All other uses of ``goto`` are diagnosed in `C++`. + + +Options +------- + +.. option:: IgnoreMacros + + If set to `true`, the check will not warn if a ``goto`` statement is + expanded from a macro. Default is `false`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/missing-std-forward.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/missing-std-forward.rst index 0c311b59a5d5a..62e38fcd3b9dc 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/missing-std-forward.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/missing-std-forward.rst @@ -35,6 +35,13 @@ Example: f(1, 2); // Incorrect - may not invoke the desired qualified function operator } +Options +------- + +.. option:: ForwardFunction + + Specify the function used for forwarding. Default is `::std::forward`. + This check implements `F.19 `_ from the C++ Core Guidelines. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/rvalue-reference-param-not-moved.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/rvalue-reference-param-not-moved.rst index ffa3a9d61e48e..2fea9f16b3bb0 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/rvalue-reference-param-not-moved.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/rvalue-reference-param-not-moved.rst @@ -79,6 +79,10 @@ Options T other = std::forward(t); } +.. option:: MoveFunction + + Specify the function used for moving. Default is `::std::move`. + This check implements `F.18 `_ from the C++ Core Guidelines. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/special-member-functions.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/special-member-functions.rst index 20f898fdab930..982d16fc8d23d 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/special-member-functions.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/special-member-functions.rst @@ -85,3 +85,8 @@ Options struct A : boost::noncopyable { ~A() { std::cout << "dtor\n"; } }; + +.. option:: IgnoreMacros + + If set to `true`, the check will not give warnings for classes defined + inside macros. Default is `true`. 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 0000000000000..9e9f4c99dc240 --- /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 5a79d61b1fd7e..5098582d0c42b 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" @@ -311,6 +312,7 @@ Clang-Tidy Checks :doc:`modernize-use-nullptr `, "Yes" :doc:`modernize-use-override `, "Yes" :doc:`modernize-use-ranges `, "Yes" + :doc:`modernize-use-scoped-lock `, "Yes" :doc:`modernize-use-starts-ends-with `, "Yes" :doc:`modernize-use-std-format `, "Yes" :doc:`modernize-use-std-numbers `, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-scoped-lock.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-scoped-lock.rst new file mode 100644 index 0000000000000..d184f1aefd806 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-scoped-lock.rst @@ -0,0 +1,101 @@ +.. title:: clang-tidy - modernize-use-scoped-lock + +modernize-use-scoped-lock +========================= + +Finds uses of ``std::lock_guard`` and suggests replacing them with C++17's +alternative ``std::scoped_lock``. + +Fix-its are provided for single declarations of ``std::lock_guard`` and warning +is emitted for multiple declarations of ``std::lock_guard`` that can be +replaced with a single declaration of ``std::scoped_lock``. + +Examples +-------- + +Single ``std::lock_guard`` declaration: + +.. code-block:: c++ + + std::mutex M; + std::lock_guard L(M); + + +Transforms to: + +.. code-block:: c++ + + std::mutex M; + std::scoped_lock L(M); + +Single ``std::lock_guard`` declaration with ``std::adopt_lock``: + +.. code-block:: c++ + + std::mutex M; + std::lock(M); + std::lock_guard L(M, std::adopt_lock); + + +Transforms to: + +.. code-block:: c++ + + std::mutex M; + std::lock(M); + std::scoped_lock L(std::adopt_lock, M); + +Multiple ``std::lock_guard`` declarations only emit warnings: + +.. code-block:: c++ + + std::mutex M1, M2; + std::lock(M1, M2); + std::lock_guard Lock1(M, std::adopt_lock); // warning: use single 'std::scoped_lock' instead of multiple 'std::lock_guard' + std::lock_guard Lock2(M, std::adopt_lock); // note: additional 'std::lock_guard' declared here + + +Limitations +----------- + +The check will not emit warnings if ``std::lock_guard`` is used implicitly via +``template`` parameter: + +.. code-block:: c++ + + template