Merge branch 'main' into p2996

This commit is contained in:
Dan Katz
2025-06-23 13:50:52 -04:00
4661 changed files with 216710 additions and 75495 deletions

View File

@@ -49,12 +49,22 @@ DEPENDENTS_TO_TEST = {
},
"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,16 @@ DEPENDENT_RUNTIMES_TO_BUILD = {"lldb": {"libcxx", "libcxxabi", "libunwind"}}
# This mapping describes runtimes that should be tested when the key project is
# touched.
DEPENDENT_RUNTIMES_TO_TEST = {"clang": {"libcxx", "libcxxabi", "libunwind"}}
DEPENDENT_RUNTIMES_TO_TEST = {
"clang": {"compiler-rt"},
"clang-tools-extra": {"libc"},
".ci": {"compiler-rt", "libc"},
}
DEPENDENT_RUNTIMES_TO_TEST_NEEDS_RECONFIG = {
"llvm": {"libcxx", "libcxxabi", "libunwind"},
"clang": {"libcxx", "libcxxabi", "libunwind"},
".ci": {"libcxx", "libcxxabi", "libunwind"},
}
EXCLUDE_LINUX = {
"cross-project-tests", # TODO(issues/132796): Tests are failing.
@@ -93,9 +112,6 @@ EXCLUDE_MAC = {
"cross-project-tests",
"flang",
"libc",
"libcxx",
"libcxxabi",
"libunwind",
"lldb",
"openmp",
"polly",
@@ -122,21 +138,35 @@ PROJECT_CHECK_TARGETS = {
"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 +184,52 @@ def _compute_projects_to_test(modified_projects: Set[str], platform: str) -> Set
):
continue
projects_to_test.add(dependent_project)
if platform == "Linux":
for to_exclude in EXCLUDE_LINUX:
if to_exclude in projects_to_test:
projects_to_test.remove(to_exclude)
elif platform == "Windows":
for to_exclude in EXCLUDE_WINDOWS:
if to_exclude in projects_to_test:
projects_to_test.remove(to_exclude)
elif platform == "Darwin":
for to_exclude in EXCLUDE_MAC:
if to_exclude in projects_to_test:
projects_to_test.remove(to_exclude)
else:
raise ValueError("Unexpected platform.")
projects_to_test = _exclude_projects(projects_to_test, platform)
return projects_to_test
def _compute_projects_to_build(projects_to_test: Set[str]) -> Set[str]:
return _add_dependencies(projects_to_test)
def _compute_projects_to_build(
projects_to_test: Set[str], runtimes: Set[str]
) -> Set[str]:
return _add_dependencies(projects_to_test, runtimes)
def _compute_project_check_targets(projects_to_test: Set[str]) -> Set[str]:
check_targets = set()
for project_to_test in projects_to_test:
if project_to_test not in PROJECT_CHECK_TARGETS:
continue
check_targets.add(PROJECT_CHECK_TARGETS[project_to_test])
if project_to_test in PROJECT_CHECK_TARGETS:
check_targets.add(PROJECT_CHECK_TARGETS[project_to_test])
return check_targets
def _compute_runtimes_to_test(projects_to_test: Set[str]) -> Set[str]:
def _compute_runtimes_to_test(modified_projects: Set[str], platform: str) -> Set[str]:
runtimes_to_test = set()
for project_to_test in projects_to_test:
if project_to_test 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 +253,19 @@ def _get_modified_projects(modified_files: list[str]) -> Set[str]:
def get_env_variables(modified_files: list[str], platform: str) -> Set[str]:
modified_projects = _get_modified_projects(modified_files)
projects_to_test = _compute_projects_to_test(modified_projects, platform)
projects_to_build = _compute_projects_to_build(projects_to_test)
runtimes_to_test = _compute_runtimes_to_test(modified_projects, platform)
runtimes_to_test_needs_reconfig = _compute_runtimes_to_test_needs_reconfig(
modified_projects, platform
)
runtimes_to_build = _compute_runtimes_to_build(
runtimes_to_test | runtimes_to_test_needs_reconfig, modified_projects, platform
)
projects_to_build = _compute_projects_to_build(projects_to_test, runtimes_to_build)
projects_check_targets = _compute_project_check_targets(projects_to_test)
runtimes_to_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 +275,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)
),
}

View File

@@ -26,6 +26,10 @@ class TestComputeProjects(unittest.TestCase):
)
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 @@ class TestComputeProjects(unittest.TestCase):
)
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 @@ class TestComputeProjects(unittest.TestCase):
)
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 @@ class TestComputeProjects(unittest.TestCase):
)
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 @@ class TestComputeProjects(unittest.TestCase):
)
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 @@ class TestComputeProjects(unittest.TestCase):
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 @@ class TestComputeProjects(unittest.TestCase):
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 @@ class TestComputeProjects(unittest.TestCase):
)
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 @@ class TestComputeProjects(unittest.TestCase):
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 @@ class TestComputeProjects(unittest.TestCase):
self.assertEqual(env_variables["project_check_targets"], "")
self.assertEqual(env_variables["runtimes_to_build"], "")
self.assertEqual(env_variables["runtimes_check_targets"], "")
self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "")
def test_top_level_file(self):
env_variables = compute_projects.get_env_variables(["README.md"], "Linux")
@@ -160,6 +185,7 @@ class TestComputeProjects(unittest.TestCase):
self.assertEqual(env_variables["project_check_targets"], "")
self.assertEqual(env_variables["runtimes_to_build"], "")
self.assertEqual(env_variables["runtimes_check_targets"], "")
self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "")
def test_exclude_runtiems_in_projects(self):
env_variables = compute_projects.get_env_variables(
@@ -169,6 +195,7 @@ class TestComputeProjects(unittest.TestCase):
self.assertEqual(env_variables["project_check_targets"], "")
self.assertEqual(env_variables["runtimes_to_build"], "")
self.assertEqual(env_variables["runtimes_check_targets"], "")
self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "")
def test_exclude_docs(self):
env_variables = compute_projects.get_env_variables(
@@ -178,6 +205,7 @@ class TestComputeProjects(unittest.TestCase):
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 +215,30 @@ class TestComputeProjects(unittest.TestCase):
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["project_check_targets"],
"check-clang check-lld check-lldb check-llvm",
env_variables["projects_to_build"],
"bolt;clang;clang-tools-extra;flang;libclc;lld;lldb;llvm;mlir;polly",
)
self.assertEqual(
env_variables["runtimes_to_build"], "libcxx;libcxxabi;libunwind"
env_variables["project_check_targets"],
"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"],
"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 +252,19 @@ class TestComputeProjects(unittest.TestCase):
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__":

View File

@@ -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 <<EOF
steps:
EOF
echo "Files modified:" >&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 <<EOF
- trigger: "libcxx-ci"
build:
message: "${buildMessage}"
commit: "${BUILDKITE_COMMIT}"
branch: "${BUILDKITE_BRANCH}"
EOF
fi
# Generic pipeline for projects that have not defined custom steps.
#
# Individual projects should instead define the pre-commit CI tests that suits their
# needs while letting them run on the infrastructure provided by LLVM.
# Figure out which projects need to be built on each platform
source <(git diff --name-only origin/${BUILDKITE_PULL_REQUEST_BASE_BRANCH}...HEAD | python3 .ci/compute_projects.py Linux)
linux_projects=${projects_to_build}
linux_check_targets=${project_check_targets}
linux_runtimes=${runtimes_to_build}
linux_runtime_check_targets=${runtimes_check_targets}
source <(git diff --name-only origin/${BUILDKITE_PULL_REQUEST_BASE_BRANCH}...HEAD | python3 .ci/compute_projects.py Windows)
windows_projects=${projects_to_build}
windows_check_targets=${project_check_targets}
# Generate the appropriate pipeline
if [[ "${linux_projects}" != "" ]]; then
cat <<EOF
- label: ':linux: Linux x64'
artifact_paths:
- 'artifacts/**/*'
- '*_result.json'
- 'build/test-results.*.xml'
agents: ${LINUX_AGENTS}
retry:
automatic:
- exit_status: -1 # Agent was lost
limit: 2
- exit_status: 255 # Forced agent shutdown
limit: 2
timeout_in_minutes: 120
env:
CC: 'clang'
CXX: 'clang++'
commands:
- './.ci/monolithic-linux.sh "$(echo ${linux_projects} | tr ' ' ';')" "$(echo ${linux_check_targets})" "$(echo ${linux_runtimes} | tr ' ' ';')" "$(echo ${linux_runtime_check_targets})"'
EOF
fi
if [[ "${windows_projects}" != "" ]]; then
cat <<EOF
- label: ':windows: Windows x64'
artifact_paths:
- 'artifacts/**/*'
- '*_result.json'
- 'build/test-results.*.xml'
agents: ${WINDOWS_AGENTS}
retry:
automatic:
- exit_status: -1 # Agent was lost
limit: 2
- exit_status: 255 # Forced agent shutdown
limit: 2
timeout_in_minutes: 150
env:
MAX_PARALLEL_COMPILE_JOBS: '16'
MAX_PARALLEL_LINK_JOBS: '4'
commands:
- 'C:\\BuildTools\\Common7\\Tools\\VsDevCmd.bat -arch=amd64 -host_arch=amd64'
- 'bash .ci/monolithic-windows.sh "$(echo ${windows_projects} | tr ' ' ';')" "$(echo ${windows_check_targets})"'
EOF
fi

View File

@@ -1,57 +0,0 @@
# 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
"""Script to generate a build report for buildkite."""
import argparse
import os
import subprocess
import generate_test_report_lib
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"title", help="Title of the test report, without Markdown formatting."
)
parser.add_argument("context", help="Annotation context to write to.")
parser.add_argument("return_code", help="The build's return code.", type=int)
parser.add_argument("junit_files", help="Paths to JUnit report files.", nargs="*")
args = parser.parse_args()
# All of these are required to build a link to download the log file.
env_var_names = [
"BUILDKITE_ORGANIZATION_SLUG",
"BUILDKITE_PIPELINE_SLUG",
"BUILDKITE_BUILD_NUMBER",
"BUILDKITE_JOB_ID",
]
buildkite_info = {k: v for k, v in os.environ.items() if k in env_var_names}
if len(buildkite_info) != len(env_var_names):
buildkite_info = None
report, style = generate_test_report_lib.generate_report_from_files(
args.title, args.return_code, args.junit_files, buildkite_info
)
if report:
p = subprocess.Popen(
[
"buildkite-agent",
"annotate",
"--context",
args.context,
"--style",
style,
],
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
# The report can be larger than the buffer for command arguments so we send
# it over stdin instead.
_, err = p.communicate(input=report)
if p.returncode:
raise RuntimeError(f"Failed to send report to buildkite-agent:\n{err}")

View File

@@ -42,14 +42,9 @@ function at-exit {
# If building fails there will be no results files.
shopt -s nullglob
if command -v buildkite-agent 2>&1 >/dev/null
then
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,6 +52,7 @@ 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"
@@ -93,9 +89,15 @@ echo "--- ninja"
# Targets are not escaped as they are passed as separate arguments.
ninja -C "${BUILD_DIR}" -k 0 ${targets}
if [[ "${runtime_targets}" != "" ]]; then
echo "--- ninja runtimes"
ninja -C "${BUILD_DIR}" ${runtime_targets}
fi
# Compiling runtimes with just-built Clang and running their tests
# as an additional testing for Clang.
if [[ "${runtimes_targets}" != "" ]]; then
if [[ "${runtime_targets_needs_reconfig}" != "" ]]; then
echo "--- cmake runtimes C++26"
cmake \
@@ -105,7 +107,7 @@ if [[ "${runtimes_targets}" != "" ]]; then
echo "--- ninja runtimes C++26"
ninja -C "${BUILD_DIR}" ${runtime_targets}
ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig}
echo "--- cmake runtimes clang modules"
@@ -116,5 +118,5 @@ if [[ "${runtimes_targets}" != "" ]]; then
echo "--- ninja runtimes clang modules"
ninja -C "${BUILD_DIR}" ${runtime_targets}
ninja -C "${BUILD_DIR}" ${runtime_targets_needs_reconfig}
fi

View File

@@ -37,14 +37,9 @@ function at-exit {
# If building fails there will be no results files.
shopt -s nullglob
if command -v buildkite-agent 2>&1 >/dev/null
then
python "${MONOREPO_ROOT}"/.ci/generate_test_report_buildkite.py ":windows: Windows x64 Test Results" \
"windows-x64-test-results" $retcode "${BUILD_DIR}"/test-results.*.xml
else
python "${MONOREPO_ROOT}"/.ci/generate_test_report_github.py ":window: Windows x64 Test Results" \
$retcode "${BUILD_DIR}"/test-results.*.xml >> $GITHUB_STEP_SUMMARY
fi
python "${MONOREPO_ROOT}"/.ci/generate_test_report_github.py ":window: Windows x64 Test Results" \
$retcode "${BUILD_DIR}"/test-results.*.xml >> $GITHUB_STEP_SUMMARY
}
trap at-exit EXIT

View File

@@ -777,6 +777,10 @@ backend:NVPTX:
- 'llvm/**/*nvptx*/**'
- 'llvm/**/*NVPTX*/**'
backend:MIPS:
- '**/*mips*'
- '**/*Mips*'
backend:RISC-V:
- clang/**/*riscv*
- clang/**/*RISCV*

View File

@@ -52,8 +52,8 @@ jobs:
cxx: [ 'clang++-21' ]
include:
- config: 'generic-gcc'
cc: 'gcc-14'
cxx: 'g++-14'
cc: 'gcc-15'
cxx: 'g++-15'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: ${{ matrix.config }}.${{ matrix.cxx }}
@@ -92,8 +92,8 @@ jobs:
cxx: [ 'clang++-21' ]
include:
- config: 'generic-gcc-cxx11'
cc: 'gcc-14'
cxx: 'g++-14'
cc: 'gcc-15'
cxx: 'g++-15'
- config: 'generic-cxx26'
cc: 'clang-20'
cxx: 'clang++-20'

View File

@@ -33,7 +33,7 @@ jobs:
with:
script: |
const failure_regex = /Process completed with exit code 1./
const preemption_regex = /The runner has received a shutdown signal/
const preemption_regex = /(The runner has received a shutdown signal)|(The operation was canceled)/
const wf_run = context.payload.workflow_run
core.notice(`Running on "${wf_run.display_title}" by @${wf_run.actor.login} (event: ${wf_run.event})\nWorkflow run URL: ${wf_run.html_url}`)

View File

@@ -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:

View File

@@ -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 @@ public:
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<MCInstReference> ClobberingInstrs;
@@ -282,6 +286,20 @@ public:
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<MCInstReference> LeakingInstrs;
public:
LeakageInfo(ArrayRef<MCInstReference> 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<PartialReport<MCPhysReg>> &Reports);
void augmentUnsafeUseReports(ArrayRef<PartialReport<MCPhysReg>> Reports);
void findUnsafeDefs(SmallVector<PartialReport<MCPhysReg>> &Reports);
void augmentUnsafeDefReports(ArrayRef<PartialReport<MCPhysReg>> Reports);
/// Process the reports which do not have to be augmented, and remove them
/// from Reports.
void handleSimpleReports(SmallVector<PartialReport<MCPhysReg>> &Reports);

View File

@@ -85,6 +85,8 @@ private:
};
friend raw_ostream &operator<<(raw_ostream &OS, const LBREntry &);
friend struct PerfSpeEventsTestHelper;
struct PerfBranchSample {
SmallVector<LBREntry, 32> LBR;
};
@@ -99,24 +101,29 @@ private:
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<uint64_t>()(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 @@ private:
/// Intermediate storage for profile data. We save the results of parsing
/// and use them later for processing and assigning profile.
std::unordered_map<Trace, TakenBranchInfo, TraceHash> BranchLBRs;
std::unordered_map<Trace, FTInfo, TraceHash> FallthroughLBRs;
std::unordered_map<Trace, TakenBranchInfo, TraceHash> TraceMap;
std::vector<std::pair<Trace, TakenBranchInfo>> Traces;
/// Pre-populated addresses of returns, coming from pre-aggregated data or
/// disassembly. Used to disambiguate call-continuation fall-throughs.
std::unordered_set<uint64_t> Returns;
std::unordered_map<uint64_t, uint64_t> BasicSamples;
std::vector<PerfMemSample> MemSamples;
@@ -200,8 +210,8 @@ private:
/// Return a vector of offsets corresponding to a trace in a function
/// if the trace is valid, std::nullopt otherwise.
std::optional<SmallVector<std::pair<uint64_t, uint64_t>, 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 @@ private:
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
@@ -379,9 +391,9 @@ private:
/// File format syntax:
/// E <event>
/// S <start> <count>
/// T <start> <end> <ft_end> <count>
/// [TR] <start> <end> <ft_end> <count>
/// B <start> <end> <count> <mispred_count>
/// [Ff] <start> <end> <count>
/// [Ffr] <start> <end> <count>
///
/// where <start>, <end>, <ft_end> have the format [<id>:]<offset>
///
@@ -392,8 +404,11 @@ private:
/// 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 <start> to <end> with a fall-through
/// to <ft_end>
/// R - an aggregated trace originating at a return
///
/// <id> - 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 +531,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

View File

@@ -48,6 +48,7 @@ extern llvm::cl::OptionCategory BinaryAnalysisCategory;
extern llvm::cl::opt<unsigned> AlignText;
extern llvm::cl::opt<unsigned> AlignFunctions;
extern llvm::cl::opt<bool> AggregateOnly;
extern llvm::cl::opt<bool> ArmSPE;
extern llvm::cl::opt<unsigned> BucketsPerLine;
extern llvm::cl::opt<bool> CompactCodeModel;
extern llvm::cl::opt<bool> DiffOnly;

View File

@@ -152,6 +152,8 @@ public:
// 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<const MCInst *, 4> SetOfRelatedInsts;
/// A state representing which registers are safe to use by an instruction
/// at a given program point.
///
@@ -195,7 +197,7 @@ struct SrcState {
/// pac-ret analysis, the expectation is that almost all return instructions
/// only use register `X30`, and therefore, this vector will probably have
/// length 1 in the second run.
std::vector<SmallPtrSet<const MCInst *, 4>> LastInstWritingReg;
std::vector<SetOfRelatedInsts> LastInstWritingReg;
/// Construct an empty state.
SrcState() {}
@@ -230,12 +232,11 @@ struct SrcState {
bool operator!=(const SrcState &RHS) const { return !((*this) == RHS); }
};
static void
printLastInsts(raw_ostream &OS,
ArrayRef<SmallPtrSet<const MCInst *, 4>> LastInstWritingReg) {
static void printInstsShort(raw_ostream &OS,
ArrayRef<SetOfRelatedInsts> Insts) {
OS << "Insts: ";
for (unsigned I = 0; I < LastInstWritingReg.size(); ++I) {
auto &Set = LastInstWritingReg[I];
for (unsigned I = 0; I < Insts.size(); ++I) {
auto &Set = Insts[I];
OS << "[" << I << "](";
for (const MCInst *MCInstP : Set)
OS << MCInstP << " ";
@@ -243,14 +244,14 @@ printLastInsts(raw_ostream &OS,
}
}
raw_ostream &operator<<(raw_ostream &OS, const SrcState &S) {
static raw_ostream &operator<<(raw_ostream &OS, const SrcState &S) {
OS << "src-state<";
if (S.empty()) {
OS << "empty";
} else {
OS << "SafeToDerefRegs: " << S.SafeToDerefRegs << ", ";
OS << "TrustedRegs: " << S.TrustedRegs << ", ";
printLastInsts(OS, S.LastInstWritingReg);
printInstsShort(OS, S.LastInstWritingReg);
}
OS << ">";
return OS;
@@ -279,7 +280,7 @@ void SrcStatePrinter::print(raw_ostream &OS, const SrcState &S) const {
OS << ", TrustedRegs: ";
RegStatePrinter.print(OS, S.TrustedRegs);
OS << ", ";
printLastInsts(OS, S.LastInstWritingReg);
printInstsShort(OS, S.LastInstWritingReg);
}
OS << ">";
}
@@ -323,13 +324,12 @@ protected:
DenseMap<const MCInst *, std::pair<MCPhysReg, const MCInst *>>
CheckerSequenceInfo;
SmallPtrSet<const MCInst *, 4> &lastWritingInsts(SrcState &S,
MCPhysReg Reg) const {
SetOfRelatedInsts &lastWritingInsts(SrcState &S, MCPhysReg Reg) const {
unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
return S.LastInstWritingReg[Index];
}
const SmallPtrSet<const MCInst *, 4> &lastWritingInsts(const SrcState &S,
MCPhysReg Reg) const {
const SetOfRelatedInsts &lastWritingInsts(const SrcState &S,
MCPhysReg Reg) const {
unsigned Index = RegsToTrackInstsFor.getIndex(Reg);
return S.LastInstWritingReg[Index];
}
@@ -430,11 +430,13 @@ protected:
}
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<MCInst &>(Point), 0, "", *BC.STI,
dbgs());
BC.InstPrinter->printInst(&Point, 0, "", *BC.STI, dbgs());
dbgs() << ", ";
P.print(dbgs(), Cur);
dbgs() << ")\n";
@@ -612,6 +614,42 @@ protected:
StringRef getAnnotationName() const { return "DataflowSrcSafetyAnalysis"; }
};
/// A helper base class for implementing a simplified counterpart of a dataflow
/// analysis for functions without CFG information.
template <typename StateTy> 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<StateTy>(Inst, StateAnnotationIndex);
}
virtual ~CFGUnawareAnalysis() { cleanStateAnnotations(); }
};
// A simplified implementation of DataflowSrcSafetyAnalysis for functions
// lacking CFG information.
//
@@ -646,15 +684,10 @@ protected:
// of instructions without labels in between. These sequences can be processed
// the same way basic blocks are processed by data-flow analysis, assuming
// pessimistically that all registers are unsafe at the start of each sequence.
class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis {
class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis,
public CFGUnawareAnalysis<SrcState> {
using SrcSafetyAnalysis::BC;
BinaryFunction &BF;
MCPlusBuilder::AllocatorIdTy AllocId;
unsigned StateAnnotationIndex;
void cleanStateAnnotations() {
for (auto &I : BF.instrs())
BC.MIB->removeAnnotation(I.second, StateAnnotationIndex);
}
/// Creates a state with all registers marked unsafe (not to be confused
/// with empty state).
@@ -666,15 +699,16 @@ public:
CFGUnawareSrcSafetyAnalysis(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor)
: SrcSafetyAnalysis(BF, RegsToTrackInstsFor), BF(BF), AllocId(AllocId) {
StateAnnotationIndex =
BC.MIB->getOrCreateAnnotationIndex("CFGUnawareSrcSafetyAnalysis");
: SrcSafetyAnalysis(BF, RegsToTrackInstsFor),
CFGUnawareAnalysis(BF, AllocId, "CFGUnawareSrcSafetyAnalysis"), BF(BF) {
}
void run() override {
SrcState S = createEntryState();
for (auto &I : BF.instrs()) {
MCInst &Inst = I.second;
if (BC.MIB->isCFI(Inst))
continue;
// If there is a label before this instruction, it is possible that it
// can be jumped-to, thus conservatively resetting S. As an exception,
@@ -687,12 +721,8 @@ public:
S = createUnsafeState();
}
// Check if we need to remove an old annotation (this is the case if
// this is the second, detailed, run of the analysis).
if (BC.MIB->hasAnnotation(Inst, StateAnnotationIndex))
BC.MIB->removeAnnotation(Inst, StateAnnotationIndex);
// Attach the state *before* this instruction executes.
BC.MIB->addAnnotation(Inst, StateAnnotationIndex, S, AllocId);
setState(Inst, S);
// Compute the state after this instruction executes.
S = computeNext(Inst, S);
@@ -700,10 +730,8 @@ public:
}
const SrcState &getStateBefore(const MCInst &Inst) const override {
return BC.MIB->getAnnotationAs<SrcState>(Inst, StateAnnotationIndex);
return getState(Inst);
}
~CFGUnawareSrcSafetyAnalysis() { cleanStateAnnotations(); }
};
std::shared_ptr<SrcSafetyAnalysis>
@@ -717,6 +745,483 @@ SrcSafetyAnalysis::create(BinaryFunction &BF,
RegsToTrackInstsFor);
}
/// A state representing which registers are safe to be used as the destination
/// operand of an authentication instruction.
///
/// Similar to SrcState, it is the responsibility of the analysis to take
/// register aliasing into account.
///
/// Depending on the implementation (such as whether FEAT_FPAC is implemented
/// by an AArch64 CPU or not), it may be possible that an authentication
/// instruction returns an invalid pointer on failure instead of terminating
/// the program immediately (assuming the program will crash as soon as that
/// pointer is dereferenced). Since few bits are usually allocated for the PAC
/// field (such as less than 16 bits on a typical AArch64 system), an attacker
/// can try every possible signature and guess the correct one if there is a
/// gadget that tells whether the particular pointer has a correct signature
/// (a so called "authentication oracle"). For that reason, it should be
/// impossible for an attacker to test if a pointer is correctly signed -
/// either the program should be terminated on authentication failure or
/// the result of authentication should not be accessible to an attacker.
///
/// Considering the instructions in forward order as they are executed, a
/// restricted set of operations can be allowed on any register containing a
/// value derived from the result of an authentication instruction until that
/// value is checked not to contain the result of a failed authentication.
/// In DstSafetyAnalysis, these rules are adapted, so that the safety property
/// for a register is computed by iterating the instructions in backward order.
/// Then the resulting properties are used at authentication instruction sites
/// to check output registers and report the particular instruction if it writes
/// to an unsafe register.
///
/// Another approach would be to simulate the above rules as-is, iterating over
/// the instructions in forward direction. To make it possible to report the
/// particular instructions as oracles, this would probably require tracking
/// references to these instructions for each register currently containing
/// sensitive data.
///
/// In DstSafetyAnalysis, the source register Xn of an instruction Inst is safe
/// if at least one of the following is true:
/// * Inst checks if Xn contains the result of a successful authentication and
/// terminates the program on failure. Note that Inst can either naturally
/// dereference Xn (load, branch, return, etc. instructions) or be the first
/// instruction of an explicit checking sequence.
/// * Inst performs safe address arithmetic AND both source and result
/// registers, as well as any temporary registers, must be safe after
/// execution of Inst (temporaries are not used on AArch64 and thus not
/// currently supported/allowed).
/// See MCPlusBuilder::analyzeAddressArithmeticsForPtrAuth for the details.
/// * Inst fully overwrites Xn with a constant.
struct DstState {
/// The set of registers whose values cannot be inspected by an attacker in
/// a way usable as an authentication oracle. The results of authentication
/// instructions should only be written to such registers.
BitVector CannotEscapeUnchecked;
/// A vector of sets, only used on the second analysis run.
/// Each element in this vector represents one of the tracked registers.
/// For each such register we track the set of first instructions that leak
/// the authenticated pointer before it was checked. This is intended to
/// provide clues on which instruction made the particular register unsafe.
///
/// Please note that the mapping from MCPhysReg values to indexes in this
/// vector is provided by RegsToTrackInstsFor field of DstSafetyAnalysis.
std::vector<SetOfRelatedInsts> 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<MCPhysReg> RegsToTrackInstsFor)
: BC(BF.getBinaryContext()), NumRegs(BC.MRI->getNumRegs()),
RegsToTrackInstsFor(RegsToTrackInstsFor) {}
virtual ~DstSafetyAnalysis() {}
static std::shared_ptr<DstSafetyAnalysis>
create(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> 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<const MCInst *, MCPhysReg> 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<MCPhysReg> getRegsMadeProtected(const MCInst &Inst,
const BitVector &LeakedRegs,
const DstState &Cur) const {
SmallVector<MCPhysReg> 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<MCPhysReg> 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<MCPhysReg> 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<MCInstReference> getLeakingInsts(const MCInst &Inst,
BinaryFunction &BF,
MCPhysReg LeakedReg) const {
const DstState &S = getStateAfter(Inst);
std::vector<MCInstReference> 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<DataflowDstSafetyAnalysis, DstState,
/*Backward=*/true, DstStatePrinter> {
using DFParent = DataflowAnalysis<DataflowDstSafetyAnalysis, DstState, true,
DstStatePrinter>;
friend DFParent;
using DstSafetyAnalysis::BC;
using DstSafetyAnalysis::computeNext;
public:
DataflowDstSafetyAnalysis(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> 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<DstState> {
using DstSafetyAnalysis::BC;
BinaryFunction &BF;
public:
CFGUnawareDstSafetyAnalysis(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> 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>
DstSafetyAnalysis::create(BinaryFunction &BF,
MCPlusBuilder::AllocatorIdTy AllocId,
ArrayRef<MCPhysReg> RegsToTrackInstsFor) {
if (BF.hasCFG())
return std::make_shared<DataflowDstSafetyAnalysis>(BF, AllocId,
RegsToTrackInstsFor);
return std::make_shared<CFGUnawareDstSafetyAnalysis>(BF, AllocId,
RegsToTrackInstsFor);
}
// This function could return PartialReport<T>, but currently T is always
// MCPhysReg, even though it is an implementation detail.
static PartialReport<MCPhysReg> make_generic_report(MCInstReference Location,
@@ -808,6 +1313,37 @@ shouldReportSigningOracle(const BinaryContext &BC, const MCInstReference &Inst,
return make_gadget_report(SigningOracleKind, Inst, *SignedReg);
}
static std::optional<PartialReport<MCPhysReg>>
shouldReportAuthOracle(const BinaryContext &BC, const MCInstReference &Inst,
const DstState &S) {
static const GadgetKind AuthOracleKind("authentication oracle found");
bool IsChecked = false;
std::optional<MCPhysReg> AuthReg =
BC.MIB->getWrittenAuthenticatedReg(Inst, IsChecked);
if (!AuthReg || IsChecked)
return std::nullopt;
LLVM_DEBUG({
traceInst(BC, "Found auth inst", Inst);
traceReg(BC, "Authenticated reg", *AuthReg);
});
if (S.empty()) {
LLVM_DEBUG({ dbgs() << " DstState is empty!\n"; });
return make_generic_report(
Inst, "Warning: no state computed for an authentication instruction "
"(possibly unreachable)");
}
LLVM_DEBUG(
{ traceRegMask(BC, "safe output registers", S.CannotEscapeUnchecked); });
if (S.CannotEscapeUnchecked[*AuthReg])
return std::nullopt;
return make_gadget_report(AuthOracleKind, Inst, *AuthReg);
}
template <typename T> static void iterateOverInstrs(BinaryFunction &BF, T Fn) {
if (BF.hasCFG()) {
for (BinaryBasicBlock &BB : BF)
@@ -840,6 +1376,9 @@ void FunctionAnalysisContext::findUnsafeUses(
});
iterateOverInstrs(BF, [&](MCInstReference Inst) {
if (BC.MIB->isCFI(Inst))
return;
const SrcState &S = Analysis->getStateBefore(Inst);
// If non-empty state was never propagated from the entry basic block
@@ -889,6 +1428,55 @@ void FunctionAnalysisContext::augmentUnsafeUseReports(
}
}
void FunctionAnalysisContext::findUnsafeDefs(
SmallVector<PartialReport<MCPhysReg>> &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<PartialReport<MCPhysReg>> Reports) {
SmallVector<MCPhysReg> 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<LeakageInfo>(
Analysis->getLeakingInsts(Location, BF, *Report.RequestedDetails));
Result.Diagnostics.emplace_back(Report.Issue, DetailedInfo);
}
}
void FunctionAnalysisContext::handleSimpleReports(
SmallVector<PartialReport<MCPhysReg>> &Reports) {
// Before re-running the detailed analysis, process the reports which do not
@@ -912,6 +1500,12 @@ void FunctionAnalysisContext::run() {
handleSimpleReports(UnsafeUses);
if (!UnsafeUses.empty())
augmentUnsafeUseReports(UnsafeUses);
SmallVector<PartialReport<MCPhysReg>> UnsafeDefs;
findUnsafeDefs(UnsafeDefs);
handleSimpleReports(UnsafeDefs);
if (!UnsafeDefs.empty())
augmentUnsafeDefReports(UnsafeDefs);
}
void Analysis::runOnFunction(BinaryFunction &BF,
@@ -1015,6 +1609,12 @@ void ClobberingInfo::print(raw_ostream &OS,
printRelatedInstrs(OS, Location, ClobberingInstrs);
}
void LeakageInfo::print(raw_ostream &OS, const MCInstReference Location) const {
OS << " The " << LeakingInstrs.size()
<< " instructions that leak the affected registers are:\n";
printRelatedInstrs(OS, Location, LeakingInstrs);
}
void GenericDiagnostic::generateReport(raw_ostream &OS,
const BinaryContext &BC) const {
printBasicInfo(OS, BC, Text);

View File

@@ -195,7 +195,7 @@ std::string createRetpolineFunctionTag(BinaryContext &BC,
TagOS << "+";
if (MemRef.DispExpr)
MemRef.DispExpr->print(TagOS, BC.AsmInfo.get());
BC.AsmInfo->printExpr(TagOS, *MemRef.DispExpr);
else
TagOS << MemRef.DispImm;

View File

@@ -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;

View File

@@ -49,6 +49,9 @@ static cl::opt<bool>
cl::desc("aggregate basic samples (without LBR info)"),
cl::cat(AggregatorCategory));
cl::opt<bool> ArmSPE("spe", cl::desc("Enable Arm SPE mode."),
cl::cat(AggregatorCategory));
static cl::opt<std::string>
ITraceAggregation("itrace",
cl::desc("Generate LBR info with perf itrace argument"),
@@ -61,6 +64,12 @@ FilterMemProfile("filter-mem-profile",
cl::init(true),
cl::cat(AggregatorCategory));
static cl::opt<bool> ParseMemProfile(
"parse-mem-profile",
cl::desc("enable memory profile parsing if it's present in the input data, "
"on by default unless `--itrace` is set."),
cl::init(true), cl::cat(AggregatorCategory));
static cl::opt<unsigned long long>
FilterPID("pid",
cl::desc("only use samples from process with specified PID"),
@@ -175,12 +184,26 @@ void DataAggregator::start() {
findPerfExecutable();
if (opts::ArmSPE) {
// pid from_ip to_ip flags
// where flags could be:
// P/M: whether branch was Predicted or Mispredicted.
// N: optionally appears when the branch was Not-Taken (ie fall-through)
// 12345 0x123/0x456/PN/-/-/8/RET/-
opts::ITraceAggregation = "bl";
opts::ParseMemProfile = true;
opts::BasicAggregation = false;
}
if (opts::BasicAggregation) {
launchPerfProcess("events without LBR",
MainEventsPPI,
launchPerfProcess("events without LBR", MainEventsPPI,
"script -F pid,event,ip",
/*Wait = */false);
/*Wait = */ false);
} else if (!opts::ITraceAggregation.empty()) {
// Disable parsing memory profile from trace data, unless requested by user.
if (!opts::ParseMemProfile.getNumOccurrences())
opts::ParseMemProfile = false;
std::string ItracePerfScriptArgs = llvm::formatv(
"script -F pid,brstack --itrace={0}", opts::ITraceAggregation);
launchPerfProcess("branch events with itrace", MainEventsPPI,
@@ -191,12 +214,9 @@ void DataAggregator::start() {
/*Wait = */ false);
}
// Note: we launch script for mem events regardless of the option, as the
// command fails fairly fast if mem events were not collected.
launchPerfProcess("mem events",
MemEventsPPI,
"script -F pid,event,addr,ip",
/*Wait = */false);
if (opts::ParseMemProfile)
launchPerfProcess("mem events", MemEventsPPI, "script -F pid,event,addr,ip",
/*Wait = */ false);
launchPerfProcess("process events", MMapEventsPPI,
"script --show-mmap-events --no-itrace",
@@ -217,7 +237,8 @@ void DataAggregator::abort() {
sys::Wait(TaskEventsPPI.PI, 1, &Error);
sys::Wait(MMapEventsPPI.PI, 1, &Error);
sys::Wait(MainEventsPPI.PI, 1, &Error);
sys::Wait(MemEventsPPI.PI, 1, &Error);
if (opts::ParseMemProfile)
sys::Wait(MemEventsPPI.PI, 1, &Error);
deleteTempFiles();
@@ -506,7 +527,8 @@ Error DataAggregator::preprocessProfile(BinaryContext &BC) {
errs() << "PERF2BOLT: failed to parse samples\n";
// Special handling for memory events
if (!prepareToParse("mem events", MemEventsPPI, MemEventsErrorCallback))
if (opts::ParseMemProfile &&
!prepareToParse("mem events", MemEventsPPI, MemEventsErrorCallback))
if (const std::error_code EC = parseMemEvents())
errs() << "PERF2BOLT: failed to parse memory events: " << EC.message()
<< '\n';
@@ -514,6 +536,9 @@ Error DataAggregator::preprocessProfile(BinaryContext &BC) {
deleteTempFiles();
heatmap:
// Sort parsed traces for faster processing.
llvm::sort(Traces, llvm::less_first());
if (!opts::HeatmapMode)
return Error::success();
@@ -589,8 +614,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);
}
@@ -718,50 +742,54 @@ bool DataAggregator::doInterBranch(BinaryFunction *FromFunc,
return true;
}
bool DataAggregator::checkReturn(uint64_t Addr) {
auto isReturn = [&](auto MI) { return MI && BC->MIB->isReturn(*MI); };
if (llvm::is_contained(Returns, Addr))
return true;
BinaryFunction *Func = getBinaryFunctionContainingAddress(Addr);
if (!Func)
return false;
const uint64_t Offset = Addr - Func->getAddress();
if (Func->hasInstructions()
? isReturn(Func->getInstructionAtOffset(Offset))
: isReturn(Func->disassembleInstructionAtOffset(Offset))) {
Returns.emplace(Addr);
return true;
}
return false;
}
bool DataAggregator::doBranch(uint64_t From, uint64_t To, uint64_t Count,
uint64_t Mispreds) {
// Returns whether \p Offset in \p Func contains a return instruction.
auto checkReturn = [&](const BinaryFunction &Func, const uint64_t Offset) {
auto isReturn = [&](auto MI) { return MI && BC->MIB->isReturn(*MI); };
return Func.hasInstructions()
? isReturn(Func.getInstructionAtOffset(Offset))
: isReturn(Func.disassembleInstructionAtOffset(Offset));
};
// Mutates \p Addr to an offset into the containing function, performing BAT
// offset translation and parent lookup.
//
// Returns the containing function (or BAT parent) and whether the address
// corresponds to a return (if \p IsFrom) or a call continuation (otherwise).
// Returns the containing function (or BAT parent).
auto handleAddress = [&](uint64_t &Addr, bool IsFrom) {
BinaryFunction *Func = getBinaryFunctionContainingAddress(Addr);
if (!Func) {
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);
@@ -771,37 +799,20 @@ bool DataAggregator::doBranch(uint64_t From, uint64_t To, uint64_t Count,
return doInterBranch(FromFunc, ToFunc, From, To, Count, Mispreds);
}
bool DataAggregator::doTrace(const LBREntry &First, const LBREntry &Second,
uint64_t Count) {
BinaryFunction *FromFunc = getBinaryFunctionContainingAddress(First.To);
BinaryFunction *ToFunc = getBinaryFunctionContainingAddress(Second.From);
bool DataAggregator::doTrace(const Trace &Trace, uint64_t Count,
bool IsReturn) {
const uint64_t From = Trace.From, To = Trace.To;
BinaryFunction *FromFunc = getBinaryFunctionContainingAddress(From);
BinaryFunction *ToFunc = getBinaryFunctionContainingAddress(To);
NumTraces += Count;
if (!FromFunc || !ToFunc) {
LLVM_DEBUG({
dbgs() << "Out of range trace starting in ";
if (FromFunc)
dbgs() << formatv("{0} @ {1:x}", *FromFunc,
First.To - FromFunc->getAddress());
else
dbgs() << Twine::utohexstr(First.To);
dbgs() << " and ending in ";
if (ToFunc)
dbgs() << formatv("{0} @ {1:x}", *ToFunc,
Second.From - ToFunc->getAddress());
else
dbgs() << Twine::utohexstr(Second.From);
dbgs() << '\n';
});
LLVM_DEBUG(dbgs() << "Out of range trace " << Trace << '\n');
NumLongRangeTraces += Count;
return false;
}
if (FromFunc != ToFunc) {
LLVM_DEBUG(dbgs() << "Invalid trace " << Trace << '\n');
NumInvalidTraces += Count;
LLVM_DEBUG({
dbgs() << "Invalid trace starting in " << FromFunc->getPrintName()
<< formatv(" @ {0:x}", First.To - FromFunc->getAddress())
<< " and ending in " << ToFunc->getPrintName()
<< formatv(" @ {0:x}\n", Second.From - ToFunc->getAddress());
});
return false;
}
@@ -809,51 +820,37 @@ bool DataAggregator::doTrace(const LBREntry &First, const LBREntry &Second,
BinaryFunction *ParentFunc = getBATParentFunction(*FromFunc);
if (!ParentFunc)
ParentFunc = FromFunc;
ParentFunc->SampleCountInBytes += Count * (Second.From - First.To);
ParentFunc->SampleCountInBytes += Count * (To - From);
const uint64_t FuncAddress = FromFunc->getAddress();
std::optional<BoltAddressTranslation::FallthroughListTy> 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<SmallVector<std::pair<uint64_t, uint64_t>, 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<std::pair<uint64_t, uint64_t>, 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;
@@ -880,8 +877,9 @@ DataAggregator::getFallthroughsInTrace(BinaryFunction &BF,
// Adjust FromBB if the first LBR is a return from the last instruction in
// the previous block (that instruction should be a call).
if (From == FromBB->getOffset() && !BF.containsAddress(FirstLBR.From) &&
!FromBB->isEntryPoint() && !FromBB->isLandingPad()) {
if (Trace.Branch != Trace::FT_ONLY && !BF.containsAddress(Trace.Branch) &&
From == FromBB->getOffset() &&
(IsReturn ? From : !(FromBB->isEntryPoint() || FromBB->isLandingPad()))) {
const BinaryBasicBlock *PrevBB =
BF.getLayout().getBlock(FromBB->getIndex() - 1);
if (PrevBB->getSuccessor(FromBB->getLabel())) {
@@ -889,10 +887,9 @@ DataAggregator::getFallthroughsInTrace(BinaryFunction &BF,
if (Instr && BC.MIB->isCall(*Instr))
FromBB = PrevBB;
else
LLVM_DEBUG(dbgs() << "invalid incoming LBR (no call): " << FirstLBR
<< '\n');
LLVM_DEBUG(dbgs() << "invalid trace (no call): " << Trace << '\n');
} else {
LLVM_DEBUG(dbgs() << "invalid incoming LBR: " << FirstLBR << '\n');
LLVM_DEBUG(dbgs() << "invalid trace: " << Trace << '\n');
}
}
@@ -911,9 +908,7 @@ DataAggregator::getFallthroughsInTrace(BinaryFunction &BF,
// Check for bad LBRs.
if (!BB->getSuccessor(NextBB->getLabel())) {
LLVM_DEBUG(dbgs() << "no fall-through for the trace:\n"
<< " " << FirstLBR << '\n'
<< " " << SecondLBR << '\n');
LLVM_DEBUG(dbgs() << "no fall-through for the trace: " << Trace << '\n');
return std::nullopt;
}
@@ -1002,9 +997,22 @@ ErrorOr<DataAggregator::LBREntry> 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);
}
@@ -1210,22 +1218,25 @@ ErrorOr<Location> 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<Location> Addr[3];
int64_t Counters[2] = {0};
/// Parse strings: record type and optionally an event name.
while (Type == INVALID || Type == EVENT_NAME) {
while (checkAndConsumeFS()) {
}
@@ -1242,23 +1253,26 @@ std::error_code DataAggregator::parseAggregatedLBREntry() {
Type = StringSwitch<AggregatedLBREntry>(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<int>;
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()) {
}
@@ -1268,6 +1282,7 @@ std::error_code DataAggregator::parseAggregatedLBREntry() {
Addr[I] = AddrOrErr.get();
}
/// Parse counters depending on entry type.
for (int I = 0; I < CounterNum; ++I) {
while (checkAndConsumeFS()) {
}
@@ -1278,11 +1293,13 @@ std::error_code DataAggregator::parseAggregatedLBREntry() {
Counters[I] = CountOrErr.get();
}
/// Expect end of line here.
if (!checkAndConsumeNewLine()) {
reportError("expected end of line");
return make_error_code(llvm::errc::io_error);
}
/// Record event name into \p EventNames and return.
if (Type == EVENT_NAME) {
EventNames.insert(EventName);
return std::error_code();
@@ -1296,6 +1313,7 @@ std::error_code DataAggregator::parseAggregatedLBREntry() {
int64_t Count = Counters[0];
int64_t Mispreds = Counters[1];
/// Record basic IP sample into \p BasicSamples and return.
if (Type == SAMPLE) {
BasicSamples[FromOffset] += Count;
NumTotalSamples += Count;
@@ -1307,30 +1325,39 @@ std::error_code DataAggregator::parseAggregatedLBREntry() {
if (ToFunc)
ToFunc->setHasProfileAvailable();
Trace Trace(FromOffset, ToOffset);
// Taken trace
if (Type == TRACE || Type == BRANCH) {
TakenBranchInfo &Info = BranchLBRs[Trace];
Info.TakenCount += Count;
Info.MispredCount += Mispreds;
/// 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");
}
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;
}
// Add fallthrough trace
if (Type != BRANCH) {
FTInfo &Info = FallthroughLBRs[Trace];
(Type == FT ? Info.InternCount : Info.ExternCount) += Count;
/// For branch type, mark Trace To to differentiate from a full trace.
if (Type == BRANCH)
Addr[2] = Location(Trace::BR_ONLY);
NumTraces += Count;
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();
}
@@ -1341,7 +1368,7 @@ bool DataAggregator::ignoreKernelInterrupt(LBREntry &LBR) const {
std::error_code DataAggregator::printLBRHeatMap() {
outs() << "PERF2BOLT: parse branch events...\n";
NamedRegionTimer T("parseBranch", "Parsing branch events", TimerGroupName,
NamedRegionTimer T("buildHeatmap", "Building heatmap", TimerGroupName,
TimerGroupDesc, opts::TimeAggregator);
if (BC->IsLinuxKernel) {
@@ -1377,12 +1404,9 @@ std::error_code DataAggregator::printLBRHeatMap() {
// Register basic samples and perf LBR addresses not covered by fallthroughs.
for (const auto &[PC, Hits] : BasicSamples)
HM.registerAddress(PC, Hits);
for (const auto &LBR : FallthroughLBRs) {
const Trace &Trace = LBR.first;
const FTInfo &Info = LBR.second;
HM.registerAddressRange(Trace.From, Trace.To,
Info.InternCount + Info.ExternCount);
}
for (const auto &[Trace, Info] : Traces)
if (Trace.To != Trace::BR_ONLY)
HM.registerAddressRange(Trace.From, Trace.To, Info.TakenCount);
if (HM.getNumInvalidRanges())
outs() << "HEATMAP: invalid traces: " << HM.getNumInvalidRanges() << '\n';
@@ -1428,22 +1452,10 @@ void DataAggregator::parseLBRSample(const PerfBranchSample &Sample,
// chronological order)
if (NeedsSkylakeFix && NumEntry <= 2)
continue;
if (NextLBR) {
// Record fall-through trace.
const uint64_t TraceFrom = LBR.To;
const uint64_t TraceTo = NextLBR->From;
const BinaryFunction *TraceBF =
getBinaryFunctionContainingAddress(TraceFrom);
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;
}
@@ -1518,7 +1530,9 @@ void DataAggregator::printBranchStacksDiagnostics(
}
std::error_code DataAggregator::parseBranchEvents() {
outs() << "PERF2BOLT: parse branch events...\n";
std::string BranchEventTypeStr =
opts::ArmSPE ? "SPE branch events in LBR-format" : "branch events";
outs() << "PERF2BOLT: parse " << BranchEventTypeStr << "...\n";
NamedRegionTimer T("parseBranch", "Parsing branch events", TimerGroupName,
TimerGroupDesc, opts::TimeAggregator);
@@ -1546,7 +1560,8 @@ std::error_code DataAggregator::parseBranchEvents() {
}
NumEntries += Sample.LBR.size();
if (BAT && Sample.LBR.size() == 32 && !NeedsSkylakeFix) {
if (this->BC->isX86() && BAT && Sample.LBR.size() == 32 &&
!NeedsSkylakeFix) {
errs() << "PERF2BOLT-WARNING: using Intel Skylake bug workaround\n";
NeedsSkylakeFix = true;
}
@@ -1554,10 +1569,14 @@ std::error_code DataAggregator::parseBranchEvents() {
parseLBRSample(Sample, NeedsSkylakeFix);
}
for (const Trace &Trace : llvm::make_first_range(BranchLBRs))
for (const uint64_t Addr : {Trace.From, Trace.To})
Traces.reserve(TraceMap.size());
for (const auto &[Trace, Info] : TraceMap) {
Traces.emplace_back(Trace, Info);
for (const uint64_t Addr : {Trace.Branch, Trace.From})
if (BinaryFunction *BF = getBinaryFunctionContainingAddress(Addr))
BF->setHasProfileAvailable();
}
clear(TraceMap);
outs() << "PERF2BOLT: read " << NumSamples << " samples and " << NumEntries
<< " LBR entries\n";
@@ -1565,10 +1584,18 @@ std::error_code DataAggregator::parseBranchEvents() {
if (NumSamples && NumSamplesNoLBR == NumSamples) {
// Note: we don't know if perf2bolt is being used to parse memory samples
// at this point. In this case, it is OK to parse zero LBRs.
errs() << "PERF2BOLT-WARNING: all recorded samples for this binary lack "
"LBR. Record profile with perf record -j any or run perf2bolt "
"in no-LBR mode with -nl (the performance improvement in -nl "
"mode may be limited)\n";
if (!opts::ArmSPE)
errs()
<< "PERF2BOLT-WARNING: all recorded samples for this binary lack "
"LBR. Record profile with perf record -j any or run perf2bolt "
"in no-LBR mode with -nl (the performance improvement in -nl "
"mode may be limited)\n";
else
errs()
<< "PERF2BOLT-WARNING: All recorded samples for this binary lack "
"SPE brstack entries. Make sure you are running Linux perf 6.14 "
"or later, otherwise you get zero samples. Record the profile "
"with: perf record -e 'arm_spe_0/branch_filter=1/'.";
} else {
printBranchStacksDiagnostics(NumTotalSamples - NumSamples);
}
@@ -1582,23 +1609,15 @@ void DataAggregator::processBranchEvents() {
NamedRegionTimer T("processBranch", "Processing branch events",
TimerGroupName, TimerGroupDesc, opts::TimeAggregator);
for (const auto &AggrLBR : FallthroughLBRs) {
const Trace &Loc = AggrLBR.first;
const FTInfo &Info = AggrLBR.second;
LBREntry First{Loc.From, Loc.From, false};
LBREntry Second{Loc.To, Loc.To, false};
if (Info.InternCount)
doTrace(First, Second, Info.InternCount);
if (Info.ExternCount) {
First.From = 0;
doTrace(First, Second, Info.ExternCount);
}
}
for (const auto &AggrLBR : BranchLBRs) {
const Trace &Loc = AggrLBR.first;
const TakenBranchInfo &Info = AggrLBR.second;
doBranch(Loc.From, Loc.To, Info.TakenCount, Info.MispredCount);
Returns.emplace(Trace::FT_EXTERNAL_RETURN);
for (const auto &[Trace, Info] : Traces) {
bool IsReturn = checkReturn(Trace.Branch);
// Ignore returns.
if (!IsReturn && Trace.Branch != Trace::FT_ONLY &&
Trace.Branch != Trace::FT_EXTERNAL_ORIGIN)
doBranch(Trace.Branch, Trace.From, Info.TakenCount, Info.MispredCount);
if (Trace.To != Trace::BR_ONLY)
doTrace(Trace, Info.TakenCount, IsReturn);
}
printBranchSamplesDiagnostics();
}

View File

@@ -432,25 +432,33 @@ public:
};
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();
// Check for global and local linux_banner symbol.
BinaryData *BD = BC.getBinaryDataByName("linux_banner");
if (!BD)
BD = BC.getBinaryDataByName("linux_banner/1");
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();
}
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() {

View File

@@ -780,14 +780,6 @@ void RewriteInstance::discoverFileObjects() {
// For local symbols we want to keep track of associated FILE symbol name for
// disambiguation by combined name.
StringRef FileSymbolName;
bool SeenFileName = false;
struct SymbolRefHash {
size_t operator()(SymbolRef const &S) const {
return std::hash<decltype(DataRefImpl::p)>{}(S.getRawDataRefImpl().p);
}
};
std::unordered_map<SymbolRef, StringRef, SymbolRefHash> SymbolToFileName;
for (const ELFSymbolRef &Symbol : InputFile->symbols()) {
Expected<StringRef> NameOrError = Symbol.getName();
if (NameOrError && NameOrError->starts_with("__asan_init")) {
@@ -806,21 +798,8 @@ void RewriteInstance::discoverFileObjects() {
if (cantFail(Symbol.getFlags()) & SymbolRef::SF_Undefined)
continue;
if (cantFail(Symbol.getType()) == SymbolRef::ST_File) {
if (cantFail(Symbol.getType()) == SymbolRef::ST_File)
FileSymbols.emplace_back(Symbol);
StringRef Name =
cantFail(std::move(NameOrError), "cannot get symbol name for file");
// Ignore Clang LTO artificial FILE symbol as it is not always generated,
// and this uncertainty is causing havoc in function name matching.
if (Name == "ld-temp.o")
continue;
FileSymbolName = Name;
SeenFileName = true;
continue;
}
if (!FileSymbolName.empty() &&
!(cantFail(Symbol.getFlags()) & SymbolRef::SF_Global))
SymbolToFileName[Symbol] = FileSymbolName;
}
// Sort symbols in the file by value. Ignore symbols from non-allocatable
@@ -1028,14 +1007,14 @@ void RewriteInstance::discoverFileObjects() {
// The <id> field is used for disambiguation of local symbols since there
// could be identical function names coming from identical file names
// (e.g. from different directories).
std::string AltPrefix;
auto SFI = SymbolToFileName.find(Symbol);
if (SymbolType == SymbolRef::ST_Function && SFI != SymbolToFileName.end())
AltPrefix = Name + "/" + std::string(SFI->second);
auto SFI = llvm::upper_bound(FileSymbols, ELFSymbolRef(Symbol));
if (SymbolType == SymbolRef::ST_Function && SFI != FileSymbols.begin()) {
StringRef FileSymbolName = cantFail(SFI[-1].getName());
if (!FileSymbolName.empty())
AlternativeName = NR.uniquify(Name + "/" + FileSymbolName.str());
}
UniqueName = NR.uniquify(Name);
if (!AltPrefix.empty())
AlternativeName = NR.uniquify(AltPrefix);
}
uint64_t SymbolSize = ELFSymbolRef(Symbol).getSize();
@@ -1294,7 +1273,7 @@ void RewriteInstance::discoverFileObjects() {
FDE->getAddressRange());
}
BC->setHasSymbolsWithFileName(SeenFileName);
BC->setHasSymbolsWithFileName(FileSymbols.size());
// Now that all the functions were created - adjust their boundaries.
adjustFunctionBoundaries();
@@ -1567,6 +1546,11 @@ void RewriteInstance::registerFragments() {
uint64_t ParentAddress{0};
// Check if containing FILE symbol is BOLT emitted synthetic symbol marking
// local fragments of global parents.
if (cantFail(FSI[-1].getName()) == getBOLTFileSymbolName())
goto registerParent;
// BOLT split fragment symbols are emitted just before the main function
// symbol.
for (ELFSymbolRef NextSymbol = Symbol; NextSymbol < StopSymbol;

View File

@@ -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 @@ public:
bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B,
CompFuncTy Comp) const override {
const auto &AArch64ExprA = cast<AArch64MCExpr>(A);
const auto &AArch64ExprB = cast<AArch64MCExpr>(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 @@ public:
if (isADR(Inst) || RelType == ELF::R_AARCH64_ADR_PREL_LO21 ||
RelType == ELF::R_AARCH64_TLSDESC_ADR_PREL21) {
return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS, Ctx);
return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS, Ctx);
} else if (isADRP(Inst) || RelType == ELF::R_AARCH64_ADR_PREL_PG_HI21 ||
RelType == ELF::R_AARCH64_ADR_PREL_PG_HI21_NC ||
RelType == ELF::R_AARCH64_TLSDESC_ADR_PAGE21 ||
@@ -1092,7 +1089,7 @@ public:
RelType == ELF::R_AARCH64_ADR_GOT_PAGE) {
// Never emit a GOT reloc, we handled this in
// RewriteInstance::readRelocations().
return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_PAGE, Ctx);
return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_PAGE, Ctx);
} else {
switch (RelType) {
case ELF::R_AARCH64_ADD_ABS_LO12_NC:
@@ -1106,18 +1103,18 @@ public:
case ELF::R_AARCH64_TLSDESC_LD64_LO12:
case ELF::R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
case ELF::R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_LO12, Ctx);
return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_LO12, Ctx);
case ELF::R_AARCH64_MOVW_UABS_G3:
return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G3, Ctx);
return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G3, Ctx);
case ELF::R_AARCH64_MOVW_UABS_G2:
case ELF::R_AARCH64_MOVW_UABS_G2_NC:
return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G2_NC, Ctx);
return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G2_NC, Ctx);
case ELF::R_AARCH64_MOVW_UABS_G1:
case ELF::R_AARCH64_MOVW_UABS_G1_NC:
return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G1_NC, Ctx);
return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G1_NC, Ctx);
case ELF::R_AARCH64_MOVW_UABS_G0:
case ELF::R_AARCH64_MOVW_UABS_G0_NC:
return AArch64MCExpr::create(Expr, AArch64MCExpr::VK_ABS_G0_NC, Ctx);
return MCSpecifierExpr::create(Expr, AArch64MCExpr::VK_ABS_G0_NC, Ctx);
default:
break;
}
@@ -1142,7 +1139,7 @@ public:
}
const MCSymbol *getTargetSymbol(const MCExpr *Expr) const override {
auto *AArchExpr = dyn_cast<AArch64MCExpr>(Expr);
auto *AArchExpr = dyn_cast<MCSpecifierExpr>(Expr);
if (AArchExpr && AArchExpr->getSubExpr())
return getTargetSymbol(AArchExpr->getSubExpr());
@@ -1162,7 +1159,7 @@ public:
}
int64_t getTargetAddend(const MCExpr *Expr) const override {
auto *AArchExpr = dyn_cast<AArch64MCExpr>(Expr);
auto *AArchExpr = dyn_cast<MCSpecifierExpr>(Expr);
if (AArchExpr && AArchExpr->getSubExpr())
return getTargetAddend(AArchExpr->getSubExpr());
@@ -2030,9 +2027,8 @@ public:
MCInst Inst;
Inst.setOpcode(AArch64::MOVZXi);
Inst.addOperand(MCOperand::createReg(AArch64::X16));
Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create(
MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx),
AArch64MCExpr::VK_ABS_G3, *Ctx)));
Inst.addOperand(MCOperand::createExpr(
MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G3, *Ctx)));
Inst.addOperand(MCOperand::createImm(0x30));
Seq.emplace_back(Inst);
@@ -2040,9 +2036,8 @@ public:
Inst.setOpcode(AArch64::MOVKXi);
Inst.addOperand(MCOperand::createReg(AArch64::X16));
Inst.addOperand(MCOperand::createReg(AArch64::X16));
Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create(
MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx),
AArch64MCExpr::VK_ABS_G2_NC, *Ctx)));
Inst.addOperand(MCOperand::createExpr(
MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G2_NC, *Ctx)));
Inst.addOperand(MCOperand::createImm(0x20));
Seq.emplace_back(Inst);
@@ -2050,9 +2045,8 @@ public:
Inst.setOpcode(AArch64::MOVKXi);
Inst.addOperand(MCOperand::createReg(AArch64::X16));
Inst.addOperand(MCOperand::createReg(AArch64::X16));
Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create(
MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx),
AArch64MCExpr::VK_ABS_G1_NC, *Ctx)));
Inst.addOperand(MCOperand::createExpr(
MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G1_NC, *Ctx)));
Inst.addOperand(MCOperand::createImm(0x10));
Seq.emplace_back(Inst);
@@ -2060,9 +2054,8 @@ public:
Inst.setOpcode(AArch64::MOVKXi);
Inst.addOperand(MCOperand::createReg(AArch64::X16));
Inst.addOperand(MCOperand::createReg(AArch64::X16));
Inst.addOperand(MCOperand::createExpr(AArch64MCExpr::create(
MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx),
AArch64MCExpr::VK_ABS_G0_NC, *Ctx)));
Inst.addOperand(MCOperand::createExpr(
MCSpecifierExpr::create(Target, AArch64MCExpr::VK_ABS_G0_NC, *Ctx)));
Inst.addOperand(MCOperand::createImm(0));
Seq.emplace_back(Inst);

View File

@@ -10,7 +10,7 @@
//
//===----------------------------------------------------------------------===//
#include "MCTargetDesc/RISCVMCExpr.h"
#include "MCTargetDesc/RISCVMCAsmInfo.h"
#include "MCTargetDesc/RISCVMCTargetDesc.h"
#include "bolt/Core/MCPlusBuilder.h"
#include "llvm/BinaryFormat/ELF.h"
@@ -33,8 +33,8 @@ public:
bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B,
CompFuncTy Comp) const override {
const auto &RISCVExprA = cast<RISCVMCExpr>(A);
const auto &RISCVExprB = cast<RISCVMCExpr>(B);
const auto &RISCVExprA = cast<MCSpecifierExpr>(A);
const auto &RISCVExprB = cast<MCSpecifierExpr>(B);
if (RISCVExprA.getSpecifier() != RISCVExprB.getSpecifier())
return false;
@@ -245,7 +245,7 @@ public:
MCContext *Ctx) {
Inst.setOpcode(Opcode);
Inst.clear();
Inst.addOperand(MCOperand::createExpr(RISCVMCExpr::create(
Inst.addOperand(MCOperand::createExpr(MCSpecifierExpr::create(
MCSymbolRefExpr::create(Target, MCSymbolRefExpr::VK_None, *Ctx),
ELF::R_RISCV_CALL_PLT, *Ctx)));
}
@@ -342,7 +342,7 @@ public:
}
const MCSymbol *getTargetSymbol(const MCExpr *Expr) const override {
auto *RISCVExpr = dyn_cast<RISCVMCExpr>(Expr);
auto *RISCVExpr = dyn_cast<MCSpecifierExpr>(Expr);
if (RISCVExpr && RISCVExpr->getSubExpr())
return getTargetSymbol(RISCVExpr->getSubExpr());
@@ -435,19 +435,19 @@ public:
case ELF::R_RISCV_TLS_GD_HI20:
// The GOT is reused so no need to create GOT relocations
case ELF::R_RISCV_PCREL_HI20:
return RISCVMCExpr::create(Expr, ELF::R_RISCV_PCREL_HI20, Ctx);
return MCSpecifierExpr::create(Expr, ELF::R_RISCV_PCREL_HI20, Ctx);
case ELF::R_RISCV_PCREL_LO12_I:
case ELF::R_RISCV_PCREL_LO12_S:
return RISCVMCExpr::create(Expr, RISCVMCExpr::VK_PCREL_LO, Ctx);
return MCSpecifierExpr::create(Expr, RISCV::S_PCREL_LO, Ctx);
case ELF::R_RISCV_HI20:
return RISCVMCExpr::create(Expr, ELF::R_RISCV_HI20, Ctx);
return MCSpecifierExpr::create(Expr, ELF::R_RISCV_HI20, Ctx);
case ELF::R_RISCV_LO12_I:
case ELF::R_RISCV_LO12_S:
return RISCVMCExpr::create(Expr, RISCVMCExpr::VK_LO, Ctx);
return MCSpecifierExpr::create(Expr, RISCV::S_LO, Ctx);
case ELF::R_RISCV_CALL:
return RISCVMCExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx);
return MCSpecifierExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx);
case ELF::R_RISCV_CALL_PLT:
return RISCVMCExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx);
return MCSpecifierExpr::create(Expr, ELF::R_RISCV_CALL_PLT, Ctx);
}
}
@@ -466,10 +466,10 @@ public:
return false;
const auto *ImmExpr = ImmOp.getExpr();
if (!isa<RISCVMCExpr>(ImmExpr))
if (!isa<MCSpecifierExpr>(ImmExpr))
return false;
switch (cast<RISCVMCExpr>(ImmExpr)->getSpecifier()) {
switch (cast<MCSpecifierExpr>(ImmExpr)->getSpecifier()) {
default:
return false;
case ELF::R_RISCV_CALL_PLT:

View File

@@ -6,12 +6,25 @@ set(version_inc "${CMAKE_CURRENT_BINARY_DIR}/VCSVersion.inc")
set(generate_vcs_version_script "${LLVM_CMAKE_DIR}/GenerateVersionFromVCS.cmake")
if(llvm_vc AND LLVM_APPEND_VC_REV)
set(llvm_source_dir ${LLVM_MAIN_SRC_DIR})
endif()
if(LLVM_VC_REPOSITORY AND LLVM_VC_REVISION)
set(llvm_source_dir ${LLVM_SOURCE_DIR})
set(llvm_vc_repository ${LLVM_VC_REPOSITORY})
set(llvm_vc_revision ${LLVM_VC_REVISION})
endif()
if(bolt_vc AND LLVM_APPEND_VC_REV)
set(bolt_source_dir ${BOLT_SOURCE_DIR})
endif()
# Create custom target to generate the VC revision include.
add_custom_command(OUTPUT "${version_inc}"
DEPENDS "${llvm_vc}" "${bolt_vc}" "${generate_vcs_version_script}"
COMMAND ${CMAKE_COMMAND} "-DNAMES=BOLT"
"-DLLVM_SOURCE_DIR=${llvm_source_dir}"
"-DBOLT_SOURCE_DIR=${bolt_source_dir}"
"-DHEADER_FILE=${version_inc}"
"-DBOLT_SOURCE_DIR=${BOLT_SOURCE_DIR}"
"-DLLVM_VC_REPOSITORY=${llvm_vc_repository}"
"-DLLVM_VC_REVISION=${llvm_vc_revision}"
"-DLLVM_FORCE_VC_REVISION=${LLVM_FORCE_VC_REVISION}"

View File

@@ -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:

View File

@@ -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
## 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 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
## 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)

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,812 @@
// RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe
// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s
// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s
// The detection of compiler-generated explicit pointer checks is tested in
// gs-pauth-address-checks.s, for that reason only test here "dummy-load" and
// "high-bits-notbi" checkers, as the shortest examples of checkers that are
// detected per-instruction and per-BB.
// PACRET-NOT: authentication oracle found in function
.text
.type sym,@function
sym:
ret
.size sym, .-sym
.globl callee
.type callee,@function
callee:
ret
.size callee, .-callee
.globl good_ret
.type good_ret,@function
good_ret:
// CHECK-NOT: good_ret
autia x0, x1
ret x0
.size good_ret, .-good_ret
.globl good_call
.type good_call,@function
good_call:
// CHECK-NOT: good_call
paciasp
stp x29, x30, [sp, #-16]!
mov x29, sp
autia x0, x1
blr x0
ldp x29, x30, [sp], #16
autiasp
ret
.size good_call, .-good_call
.globl good_branch
.type good_branch,@function
good_branch:
// CHECK-NOT: good_branch
autia x0, x1
br x0
.size good_branch, .-good_branch
.globl good_load_other_reg
.type good_load_other_reg,@function
good_load_other_reg:
// CHECK-NOT: good_load_other_reg
autia x0, x1
ldr x2, [x0]
ret
.size good_load_other_reg, .-good_load_other_reg
.globl good_load_same_reg
.type good_load_same_reg,@function
good_load_same_reg:
// CHECK-NOT: good_load_same_reg
autia x0, x1
ldr x0, [x0]
ret
.size good_load_same_reg, .-good_load_same_reg
.globl good_explicit_check
.type good_explicit_check,@function
good_explicit_check:
// CHECK-NOT: good_explicit_check
autia x0, x1
eor x16, x0, x0, lsl #1
tbz x16, #62, 1f
brk 0x1234
1:
ret
.size good_explicit_check, .-good_explicit_check
.globl bad_unchecked
.type bad_unchecked,@function
bad_unchecked:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 0 instructions that leak the affected registers are:
autia x0, x1
ret
.size bad_unchecked, .-bad_unchecked
.globl bad_leaked_to_subroutine
.type bad_leaked_to_subroutine,@function
bad_leaked_to_subroutine:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl callee
// CHECK-NEXT: This happens in the following basic block:
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: {{[0-9a-f]+}}: bl callee
// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
// CHECK-NEXT: {{[0-9a-f]+}}: autiasp
// CHECK-NEXT: {{[0-9a-f]+}}: ret
paciasp
stp x29, x30, [sp, #-16]!
mov x29, sp
autia x0, x1
bl callee
ldr x2, [x0]
ldp x29, x30, [sp], #16
autiasp
ret
.size bad_leaked_to_subroutine, .-bad_leaked_to_subroutine
.globl bad_unknown_usage_read
.type bad_unknown_usage_read,@function
bad_unknown_usage_read:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul x3, x0, x1
// CHECK-NEXT: This happens in the following basic block:
// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: {{[0-9a-f]+}}: mul x3, x0, x1
// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
// CHECK-NEXT: {{[0-9a-f]+}}: ret
autia x0, x1
// Registers are not accessible to an attacker under Pointer
// Authentication threat model, until spilled to memory.
// Thus, reporting the below MUL instruction is a false positive, since
// the next LDR instruction prevents any possible spilling of x3 unless
// the authentication succeeded. Though, rejecting anything except for
// a closed list of instruction types is the intended behavior of the
// analysis, so this false positive is by design.
mul x3, x0, x1
ldr x2, [x0]
ret
.size bad_unknown_usage_read, .-bad_unknown_usage_read
.globl bad_store_to_memory_and_wait
.type bad_store_to_memory_and_wait,@function
bad_store_to_memory_and_wait:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_store_to_memory_and_wait, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: str x0, [x3]
autia x0, x1
cbz x3, 2f
str x0, [x3]
1:
// The thread performs a time-consuming computation while the result of
// authentication is accessible in memory.
nop
2:
ldr x2, [x0]
ret
.size bad_store_to_memory_and_wait, .-bad_store_to_memory_and_wait
// FIXME: Known false negative: if no return instruction is reachable from a
// program point (this probably implies an infinite loop), such
// instruction cannot be detected as an authentication oracle.
.globl bad_store_to_memory_and_hang
.type bad_store_to_memory_and_hang,@function
bad_store_to_memory_and_hang:
// CHECK-NOT: bad_store_to_memory_and_hang
autia x0, x1
cbz x3, 2f
str x0, [x3]
1:
// The thread loops indefinitely while the result of authentication
// is accessible in memory.
b 1b
2:
ldr x2, [x0]
ret
.size bad_store_to_memory_and_hang, .-bad_store_to_memory_and_hang
.globl bad_unknown_usage_subreg_read
.type bad_unknown_usage_subreg_read,@function
bad_unknown_usage_subreg_read:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul w3, w0, w1
// CHECK-NEXT: This happens in the following basic block:
// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: {{[0-9a-f]+}}: mul w3, w0, w1
// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
// CHECK-NEXT: {{[0-9a-f]+}}: ret
autia x0, x1
mul w3, w0, w1
ldr x2, [x0]
ret
.size bad_unknown_usage_subreg_read, .-bad_unknown_usage_subreg_read
.globl bad_unknown_usage_update
.type bad_unknown_usage_update,@function
bad_unknown_usage_update:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16
// CHECK-NEXT: This happens in the following basic block:
// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16
// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
// CHECK-NEXT: {{[0-9a-f]+}}: ret
autia x0, x1
movk x0, #42, lsl #16 // does not overwrite x0 completely
ldr x2, [x0]
ret
.size bad_unknown_usage_update, .-bad_unknown_usage_update
.globl good_overwrite_with_constant
.type good_overwrite_with_constant,@function
good_overwrite_with_constant:
// CHECK-NOT: good_overwrite_with_constant
autia x0, x1
mov x0, #42
ret
.size good_overwrite_with_constant, .-good_overwrite_with_constant
// Overwriting sensitive data by instructions with unmodelled side-effects is
// explicitly rejected, even though this particular MRS is safe.
.globl bad_overwrite_with_side_effects
.type bad_overwrite_with_side_effects,@function
bad_overwrite_with_side_effects:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_overwrite_with_side_effects, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 0 instructions that leak the affected registers are:
autia x0, x1
mrs x0, CTR_EL0
ret
.size bad_overwrite_with_side_effects, .-bad_overwrite_with_side_effects
// Here the new value written by MUL to x0 is completely unrelated to the result
// of authentication, so this is a false positive.
// FIXME: Can/should we generalize overwriting by constant to handle such cases?
.globl good_unknown_overwrite
.type good_unknown_overwrite,@function
good_unknown_overwrite:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_unknown_overwrite, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 0 instructions that leak the affected registers are:
autia x0, x1
mul x0, x1, x2
ret
.size good_unknown_overwrite, .-good_unknown_overwrite
// This is a false positive: when a general-purpose register is written to as
// a 32-bit register, its top 32 bits are zeroed, but according to LLVM
// representation, the instruction only overwrites the Wn register.
.globl good_wreg_overwrite
.type good_wreg_overwrite,@function
good_wreg_overwrite:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_wreg_overwrite, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
autia x0, x1
mov w0, #42
ret
.size good_wreg_overwrite, .-good_wreg_overwrite
.globl good_address_arith
.type good_address_arith,@function
good_address_arith:
// CHECK-NOT: good_address_arith
autia x0, x1
add x1, x0, #8
sub x2, x1, #16
mov x3, x2
ldr x4, [x3]
mov x0, #0
mov x1, #0
mov x2, #0
ret
.size good_address_arith, .-good_address_arith
.globl good_ret_multi_bb
.type good_ret_multi_bb,@function
good_ret_multi_bb:
// CHECK-NOT: good_ret_multi_bb
autia x0, x1
cbz x1, 1f
nop
1:
ret x0
.size good_ret_multi_bb, .-good_ret_multi_bb
.globl good_call_multi_bb
.type good_call_multi_bb,@function
good_call_multi_bb:
// CHECK-NOT: good_call_multi_bb
paciasp
stp x29, x30, [sp, #-16]!
mov x29, sp
autia x0, x1
cbz x1, 1f
nop
1:
blr x0
cbz x1, 2f
nop
2:
ldp x29, x30, [sp], #16
autiasp
ret
.size good_call_multi_bb, .-good_call_multi_bb
.globl good_branch_multi_bb
.type good_branch_multi_bb,@function
good_branch_multi_bb:
// CHECK-NOT: good_branch_multi_bb
autia x0, x1
cbz x1, 1f
nop
1:
br x0
.size good_branch_multi_bb, .-good_branch_multi_bb
.globl good_load_other_reg_multi_bb
.type good_load_other_reg_multi_bb,@function
good_load_other_reg_multi_bb:
// CHECK-NOT: good_load_other_reg_multi_bb
autia x0, x1
cbz x1, 1f
nop
1:
ldr x2, [x0]
cbz x1, 2f
nop
2:
ret
.size good_load_other_reg_multi_bb, .-good_load_other_reg_multi_bb
.globl good_load_same_reg_multi_bb
.type good_load_same_reg_multi_bb,@function
good_load_same_reg_multi_bb:
// CHECK-NOT: good_load_same_reg_multi_bb
autia x0, x1
cbz x1, 1f
nop
1:
ldr x0, [x0]
cbz x1, 2f
nop
2:
ret
.size good_load_same_reg_multi_bb, .-good_load_same_reg_multi_bb
.globl good_explicit_check_multi_bb
.type good_explicit_check_multi_bb,@function
good_explicit_check_multi_bb:
// CHECK-NOT: good_explicit_check_multi_bb
autia x0, x1
cbz x1, 1f
nop
1:
eor x16, x0, x0, lsl #1
tbz x16, #62, 2f
brk 0x1234
2:
cbz x1, 3f
nop
3:
ret
.size good_explicit_check_multi_bb, .-good_explicit_check_multi_bb
.globl bad_unchecked_multi_bb
.type bad_unchecked_multi_bb,@function
bad_unchecked_multi_bb:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked_multi_bb, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 0 instructions that leak the affected registers are:
autia x0, x1
cbz x1, 1f
ldr x2, [x0]
1:
ret
.size bad_unchecked_multi_bb, .-bad_unchecked_multi_bb
.globl bad_leaked_to_subroutine_multi_bb
.type bad_leaked_to_subroutine_multi_bb,@function
bad_leaked_to_subroutine_multi_bb:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine_multi_bb, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl callee
paciasp
stp x29, x30, [sp, #-16]!
mov x29, sp
autia x0, x1
cbz x1, 1f
ldr x2, [x0]
1:
bl callee
ldr x2, [x0]
ldp x29, x30, [sp], #16
autiasp
ret
.size bad_leaked_to_subroutine_multi_bb, .-bad_leaked_to_subroutine_multi_bb
.globl bad_unknown_usage_read_multi_bb
.type bad_unknown_usage_read_multi_bb,@function
bad_unknown_usage_read_multi_bb:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read_multi_bb, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul x3, x0, x1
autia x0, x1
cbz x3, 1f
mul x3, x0, x1
1:
ldr x2, [x0]
ret
.size bad_unknown_usage_read_multi_bb, .-bad_unknown_usage_read_multi_bb
.globl bad_unknown_usage_subreg_read_multi_bb
.type bad_unknown_usage_subreg_read_multi_bb,@function
bad_unknown_usage_subreg_read_multi_bb:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read_multi_bb, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul w3, w0, w1
autia x0, x1
cbz x3, 1f
mul w3, w0, w1
1:
ldr x2, [x0]
ret
.size bad_unknown_usage_subreg_read_multi_bb, .-bad_unknown_usage_subreg_read_multi_bb
.globl bad_unknown_usage_update_multi_bb
.type bad_unknown_usage_update_multi_bb,@function
bad_unknown_usage_update_multi_bb:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update_multi_bb, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16
autia x0, x1
cbz x3, 1f
movk x0, #42, lsl #16 // does not overwrite x0 completely
1:
ldr x2, [x0]
ret
.size bad_unknown_usage_update_multi_bb, .-bad_unknown_usage_update_multi_bb
.globl good_overwrite_with_constant_multi_bb
.type good_overwrite_with_constant_multi_bb,@function
good_overwrite_with_constant_multi_bb:
// CHECK-NOT: good_overwrite_with_constant_multi_bb
autia x0, x1
cbz x3, 1f
1:
mov x0, #42
ret
.size good_overwrite_with_constant_multi_bb, .-good_overwrite_with_constant_multi_bb
.globl good_address_arith_multi_bb
.type good_address_arith_multi_bb,@function
good_address_arith_multi_bb:
// CHECK-NOT: good_address_arith_multi_bb
autia x0, x1
cbz x3, 1f
add x1, x0, #8
sub x2, x1, #16
mov x0, x2
mov x1, #0
mov x2, #0
1:
ldr x3, [x0]
ret
.size good_address_arith_multi_bb, .-good_address_arith_multi_bb
// FIXME: Most *_nocfg test cases contain paciasp+autiasp instructions even if
// LR is not spilled - this is a workaround for RET instructions being
// reported as non-protected, because LR state is reset at every label.
.globl good_ret_nocfg
.type good_ret_nocfg,@function
good_ret_nocfg:
// CHECK-NOT: good_ret_nocfg
adr x2, 1f
br x2
1:
autia x0, x1
ret x0
.size good_ret_nocfg, .-good_ret_nocfg
.globl good_call_nocfg
.type good_call_nocfg,@function
good_call_nocfg:
// CHECK-NOT: good_call_nocfg
paciasp
stp x29, x30, [sp, #-16]!
mov x29, sp
adr x2, 1f
br x2
1:
autia x0, x1
blr x0
ldp x29, x30, [sp], #16
autiasp
ret
.size good_call_nocfg, .-good_call_nocfg
.globl good_branch_nocfg
.type good_branch_nocfg,@function
good_branch_nocfg:
// CHECK-NOT: good_branch_nocfg
adr x2, 1f
br x2
1:
autia x0, x1
br x0
.size good_branch_nocfg, .-good_branch_nocfg
.globl good_load_other_reg_nocfg
.type good_load_other_reg_nocfg,@function
good_load_other_reg_nocfg:
// CHECK-NOT: good_load_other_reg_nocfg
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
ldr x2, [x0]
autiasp
ret
.size good_load_other_reg_nocfg, .-good_load_other_reg_nocfg
.globl good_load_same_reg_nocfg
.type good_load_same_reg_nocfg,@function
good_load_same_reg_nocfg:
// CHECK-NOT: good_load_same_reg_nocfg
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
ldr x0, [x0]
autiasp
ret
.size good_load_same_reg_nocfg, .-good_load_same_reg_nocfg
// FIXME: Multi-instruction checker sequences are not supported without CFG.
.globl bad_unchecked_nocfg
.type bad_unchecked_nocfg,@function
bad_unchecked_nocfg:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked_nocfg, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 0 instructions that leak the affected registers are:
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
autiasp
ret
.size bad_unchecked_nocfg, .-bad_unchecked_nocfg
.globl bad_leaked_to_subroutine_nocfg
.type bad_leaked_to_subroutine_nocfg,@function
bad_leaked_to_subroutine_nocfg:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine_nocfg, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl callee # Offset: 24
paciasp
stp x29, x30, [sp, #-16]!
mov x29, sp
adr x2, 1f
br x2
1:
autia x0, x1
bl callee
ldr x2, [x0]
ldp x29, x30, [sp], #16
autiasp
ret
.size bad_leaked_to_subroutine_nocfg, .-bad_leaked_to_subroutine_nocfg
.globl bad_unknown_usage_read_nocfg
.type bad_unknown_usage_read_nocfg,@function
bad_unknown_usage_read_nocfg:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read_nocfg, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul x3, x0, x1
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
mul x3, x0, x1
ldr x2, [x0]
autiasp
ret
.size bad_unknown_usage_read_nocfg, .-bad_unknown_usage_read_nocfg
.globl bad_unknown_usage_subreg_read_nocfg
.type bad_unknown_usage_subreg_read_nocfg,@function
bad_unknown_usage_subreg_read_nocfg:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read_nocfg, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul w3, w0, w1
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
mul w3, w0, w1
ldr x2, [x0]
autiasp
ret
.size bad_unknown_usage_subreg_read_nocfg, .-bad_unknown_usage_subreg_read_nocfg
.globl bad_unknown_usage_update_nocfg
.type bad_unknown_usage_update_nocfg,@function
bad_unknown_usage_update_nocfg:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update_nocfg, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
// CHECK-NEXT: The 1 instructions that leak the affected registers are:
// CHECK-NEXT: 1. {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
movk x0, #42, lsl #16 // does not overwrite x0 completely
ldr x2, [x0]
autiasp
ret
.size bad_unknown_usage_update_nocfg, .-bad_unknown_usage_update_nocfg
.globl good_overwrite_with_constant_nocfg
.type good_overwrite_with_constant_nocfg,@function
good_overwrite_with_constant_nocfg:
// CHECK-NOT: good_overwrite_with_constant_nocfg
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
mov x0, #42
autiasp
ret
.size good_overwrite_with_constant_nocfg, .-good_overwrite_with_constant_nocfg
.globl good_address_arith_nocfg
.type good_address_arith_nocfg,@function
good_address_arith_nocfg:
// CHECK-NOT: good_address_arith_nocfg
paciasp
adr x2, 1f
br x2
1:
autia x0, x1
add x1, x0, #8
sub x2, x1, #16
mov x3, x2
ldr x4, [x3]
mov x0, #0
mov x1, #0
mov x2, #0
autiasp
ret
.size good_address_arith_nocfg, .-good_address_arith_nocfg
.globl good_explicit_check_unrelated_reg
.type good_explicit_check_unrelated_reg,@function
good_explicit_check_unrelated_reg:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_explicit_check_unrelated_reg, basic block {{[^,]+}}, at address
// FIXME: The below instruction is not an authentication oracle
autia x2, x3 // One of possible execution paths after this instruction
// ends at BRK below, thus BRK used as a trap instruction
// should formally "check everything" not to introduce
// false-positive here.
autia x0, x1
eor x16, x0, x0, lsl #1
tbz x16, #62, 1f
brk 0x1234
1:
ldr x4, [x2] // Right before this instruction X2 is checked - this
// should be propagated to the basic block ending with
// TBZ instruction above.
ret
.size good_explicit_check_unrelated_reg, .-good_explicit_check_unrelated_reg
// The last BB (in layout order) is processed first by the data-flow analysis.
// Its initial state is usually filled in a special way (because it ends with
// `ret` instruction), and then affects the state propagated to the other BBs
// Thus, the case of the last instruction in a function being a jump somewhere
// in the middle is special.
.globl good_no_ret_from_last_bb
.type good_no_ret_from_last_bb,@function
good_no_ret_from_last_bb:
// CHECK-NOT: good_no_ret_from_last_bb
paciasp
autiasp // authenticates LR
b 2f
1:
ret
2:
b 1b // LR is dereferenced by `ret`, which is executed next
.size good_no_ret_from_last_bb, .-good_no_ret_from_last_bb
.globl bad_no_ret_from_last_bb
.type bad_no_ret_from_last_bb,@function
bad_no_ret_from_last_bb:
// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_no_ret_from_last_bb, basic block {{[^,]+}}, at address
// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autiasp
// CHECK-NEXT: The 0 instructions that leak the affected registers are:
paciasp
autiasp // authenticates LR
b 2f
1:
ret x0
2:
b 1b // X0 (but not LR) is dereferenced by `ret x0`
.size bad_no_ret_from_last_bb, .-bad_no_ret_from_last_bb
// Test that combined auth+something instructions are not reported as
// authentication oracles.
.globl inst_retaa
.type inst_retaa,@function
inst_retaa:
// CHECK-NOT: inst_retaa
paciasp
retaa
.size inst_retaa, .-inst_retaa
.globl inst_blraa
.type inst_blraa,@function
inst_blraa:
// CHECK-NOT: inst_blraa
paciasp
stp x29, x30, [sp, #-16]!
mov x29, sp
blraa x0, x1
ldp x29, x30, [sp], #16
retaa
.size inst_blraa, .-inst_blraa
.globl inst_braa
.type inst_braa,@function
inst_braa:
// CHECK-NOT: inst_braa
braa x0, x1
.size inst_braa, .-inst_braa
.globl inst_ldraa_no_wb
.type inst_ldraa_no_wb,@function
inst_ldraa_no_wb:
// CHECK-NOT: inst_ldraa_no_wb
ldraa x1, [x0]
ret
.size inst_ldraa_no_wb, .-inst_ldraa_no_wb
.globl inst_ldraa_wb
.type inst_ldraa_wb,@function
inst_ldraa_wb:
// CHECK-NOT: inst_ldraa_wb
ldraa x1, [x0]!
ret
.size inst_ldraa_wb, .-inst_ldraa_wb
.globl main
.type main,@function
main:
mov x0, 0
ret
.size main, .-main

View File

@@ -113,7 +113,7 @@ simple:
// CHECK-EMPTY:
// PAUTH-NEXT: Found sign inst: 00000000: paciasp # DataflowSrcSafetyAnalysis: src-state<SafeToDerefRegs: BitVector, TrustedRegs: BitVector, Insts: >
// 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<SafeToDerefRegs: BitVector, TrustedRegs: BitVector, Insts: >
// PAUTH-NEXT: Call destination reg: X0
// PAUTH-NEXT: SafeToDerefRegs: W0 X0 W0_HI{{[ \t]*$}}
@@ -220,10 +220,10 @@ nocfg:
// CHECK-EMPTY:
// PAUTH-NEXT: Found call inst: 00000000: br x0 # UNKNOWN CONTROL FLOW # Offset: 4 # CFGUnawareSrcSafetyAnalysis: src-state<SafeToDerefRegs: BitVector, TrustedRegs: BitVector, Insts: >
// 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<SafeToDerefRegs: BitVector, TrustedRegs: BitVector, Insts: >
// CHECK-NEXT: RetReg: LR
// CHECK-NEXT: SafeToDerefRegs:
// CHECK-NEXT: SafeToDerefRegs:{{[ \t]*$}}
// CHECK-EMPTY:
// CHECK-NEXT: Running detailed src register safety analysis...
// CHECK-NEXT: SrcSafetyAnalysis::ComputeNext( adr x0, __ENTRY_nocfg@0x[[ENTRY_ADDR]], src-state<SafeToDerefRegs: LR W30 W30_HI , TrustedRegs: LR W30 W30_HI , Insts: [0]()>)
@@ -251,6 +251,116 @@ nocfg:
// CHECK-EMPTY:
// CHECK-NEXT: Attaching clobbering info to: 00000000: ret # Offset: 8 # CFGUnawareSrcSafetyAnalysis: src-state<SafeToDerefRegs: BitVector, TrustedRegs: BitVector, Insts: [0]()>
.globl auth_oracle
.type auth_oracle,@function
auth_oracle:
autia x0, x1
ret
.size auth_oracle, .-auth_oracle
// CHECK-LABEL:Analyzing function auth_oracle, AllocatorId = 1
// CHECK-NEXT: Binary Function "auth_oracle" {
// CHECK-NEXT: Number : 4
// CHECK-NEXT: State : CFG constructed
// ...
// CHECK: BB Layout : [[BB0:[0-9a-zA-Z.]+]]
// CHECK-NEXT: }
// CHECK-NEXT: [[BB0]] (2 instructions, align : 1)
// CHECK-NEXT: Entry Point
// CHECK-NEXT: 00000000: autia x0, x1
// CHECK-NEXT: 00000004: ret
// CHECK-EMPTY:
// CHECK-NEXT: DWARF CFI Instructions:
// CHECK-NEXT: <empty>
// CHECK-NEXT: End of Function "auth_oracle"
// CHECK-EMPTY:
// CHECK-NEXT: Running src register safety analysis...
// ...
// CHECK: After src register safety analysis:
// CHECK-NEXT: Binary Function "auth_oracle" {
// ...
// CHECK: End of Function "auth_oracle"
// ...
// PAUTH: Running dst register safety analysis...
// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( ret x30, dst-state<CannotEscapeUnchecked: , Insts: >)
// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >)
// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >)
// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: >)
// 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<CannotEscapeUnchecked: BitVector, Insts: >
// PAUTH-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: >
// PAUTH-EMPTY:
// PAUTH-NEXT: DWARF CFI Instructions:
// PAUTH-NEXT: <empty>
// PAUTH-NEXT: End of Function "auth_oracle"
// PAUTH-EMPTY:
// PAUTH-NEXT: Found auth inst: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: >
// 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<CannotEscapeUnchecked: , Insts: [0]()>)
// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0]()>)
// PAUTH-NEXT: DstSafetyAnalysis::ComputeNext( autia x0, x1, dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0]()>)
// PAUTH-NEXT: .. result: (dst-state<CannotEscapeUnchecked: LR W30 W30_HI , Insts: [0](0x{{[0-9a-f]+}} )>)
// 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<CannotEscapeUnchecked: BitVector, Insts: [0](0x{{[0-9a-f]+}} )>
// PAUTH-NEXT: 00000004: ret # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0]()>
// PAUTH-EMPTY:
// PAUTH-NEXT: DWARF CFI Instructions:
// PAUTH-NEXT: <empty>
// PAUTH-NEXT: End of Function "auth_oracle"
// PAUTH-EMPTY:
// PAUTH-NEXT: Attaching leakage info to: 00000000: autia x0, x1 # DataflowDstSafetyAnalysis: dst-state<CannotEscapeUnchecked: BitVector, Insts: [0](0x{{[0-9a-f]+}} )>
// Gadget scanner should not crash on CFI instructions, including when debug-printing them.
// Note that the particular debug output is not checked, but BOLT should be
// compiled with assertions enabled to support -debug-only argument.
.globl cfi_inst_df
.type cfi_inst_df,@function
cfi_inst_df:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
add sp, sp, #16
.cfi_def_cfa_offset 0
ret
.size cfi_inst_df, .-cfi_inst_df
.cfi_endproc
.globl cfi_inst_nocfg
.type cfi_inst_nocfg,@function
cfi_inst_nocfg:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
adr x0, 1f
br x0
1:
add sp, sp, #16
.cfi_def_cfa_offset 0
ret
.size cfi_inst_nocfg, .-cfi_inst_nocfg
.cfi_endproc
// CHECK-LABEL:Analyzing function main, AllocatorId = 1
.globl main
.type main,@function

View File

@@ -36,9 +36,9 @@ prefix_pat = re.compile(f"^# {args.prefix}: (.*)")
fdata_pat = re.compile(r"([01].*) (?P<mispred>\d+) (?P<exec>\d+)")
# Pre-aggregated profile:
# {T|S|E|B|F|f} <start> [<end>] [<ft_end>] <count> [<mispred_count>]
# {T|R|S|E|B|F|f|r} <start> [<end>] [<ft_end>] <count> [<mispred_count>]
# <loc>: [<id>:]<offset>
preagg_pat = re.compile(r"(?P<type>[TSBFf]) (?P<offsets_count>.*)")
preagg_pat = re.compile(r"(?P<type>[TRSBFfr]) (?P<offsets_count>.*)")
# No-LBR profile:
# <is symbol?> <closest elf symbol or DSO name> <relative address> <count>

View File

@@ -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}"))

View File

@@ -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

View File

@@ -0,0 +1,9 @@
## Check that Arm SPE mode is unavailable on X86.
REQUIRES: system-linux,x86_64-linux
RUN: %clang %cflags %p/../../Inputs/asm_foo.s %p/../../Inputs/asm_main.c -o %t.exe
RUN: touch %t.empty.perf.data
RUN: not perf2bolt -p %t.empty.perf.data -o %t.perf.boltdata --spe --pa %t.exe 2>&1 | FileCheck %s
CHECK: perf2bolt{{.*}} -spe is available only on AArch64.

View File

@@ -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

View File

@@ -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()

View File

@@ -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<std::string> ReadPerfEvents;
extern cl::opt<bool> 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<typename ELF64LE::Ehdr *>(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<orc::SymbolStringPool>(),
ObjFile->getFileName(), nullptr, /*IsPIC*/ false,
DWARFContext::create(*ObjFile.get()), {llvm::outs(), llvm::errs()}));
ASSERT_FALSE(!BC);
}
char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {};
std::unique_ptr<ObjectFile> ObjFile;
std::unique_ptr<BinaryContext> BC;
/// Helper function to export lists to show the mismatch.
void reportBrStackEventMismatch(
const std::vector<std::pair<Trace, TakenBranchInfo>> &Traces,
const std::vector<std::pair<Trace, TakenBranchInfo>> &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<std::pair<Trace, TakenBranchInfo>> &ExpectedSamples) {
DataAggregator DA("<pseudo input>");
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<std::pair<Trace, TakenBranchInfo>> 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

View File

@@ -54,10 +54,8 @@ static llvm::Error decodeRecord(const Record &R, AccessSpecifier &Field,
case AS_none:
Field = (AccessSpecifier)R[0];
return llvm::Error::success();
default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"invalid value for AccessSpecifier");
}
llvm_unreachable("invalid value for AccessSpecifier");
}
static llvm::Error decodeRecord(const Record &R, TagTypeKind &Field,
@@ -94,6 +92,7 @@ static llvm::Error decodeRecord(const Record &R, InfoType &Field,
case InfoType::IT_default:
case InfoType::IT_enum:
case InfoType::IT_typedef:
case InfoType::IT_concept:
Field = IT;
return llvm::Error::success();
}
@@ -110,6 +109,7 @@ static llvm::Error decodeRecord(const Record &R, FieldId &Field,
case FieldId::F_type:
case FieldId::F_child_namespace:
case FieldId::F_child_record:
case FieldId::F_concept:
case FieldId::F_default:
Field = F;
return llvm::Error::success();
@@ -393,6 +393,29 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
"invalid field for TemplateParamInfo");
}
static llvm::Error parseRecord(const Record &R, unsigned ID,
llvm::StringRef Blob, ConceptInfo *I) {
switch (ID) {
case CONCEPT_USR:
return decodeRecord(R, I->USR, Blob);
case CONCEPT_NAME:
return decodeRecord(R, I->Name, Blob);
case CONCEPT_IS_TYPE:
return decodeRecord(R, I->IsType, Blob);
case CONCEPT_CONSTRAINT_EXPRESSION:
return decodeRecord(R, I->ConstraintExpression, Blob);
}
llvm_unreachable("invalid field for ConceptInfo");
}
static llvm::Error parseRecord(const Record &R, unsigned ID,
llvm::StringRef Blob, ConstraintInfo *I) {
if (ID == CONSTRAINT_EXPRESSION)
return decodeRecord(R, I->ConstraintExpr, Blob);
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"invalid field for ConstraintInfo");
}
template <typename T> static llvm::Expected<CommentInfo *> getCommentInfo(T I) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"invalid type cannot contain CommentInfo");
@@ -431,6 +454,10 @@ template <> llvm::Expected<CommentInfo *> getCommentInfo(CommentInfo *I) {
return I->Children.back().get();
}
template <> llvm::Expected<CommentInfo *> getCommentInfo(ConceptInfo *I) {
return &I->Description.emplace_back();
}
// When readSubBlock encounters a TypeInfo sub-block, it calls addTypeInfo on
// the parent block to set it. The template specializations define what to do
// for each supported parent block.
@@ -586,6 +613,17 @@ template <> llvm::Error addReference(RecordInfo *I, Reference &&R, FieldId F) {
}
}
template <>
llvm::Error addReference(ConstraintInfo *I, Reference &&R, FieldId F) {
if (F == FieldId::F_concept) {
I->ConceptRef = std::move(R);
return llvm::Error::success();
}
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
"ConstraintInfo cannot contain this Reference");
}
template <typename T, typename ChildInfoType>
static void addChild(T I, ChildInfoType &&R) {
llvm::errs() << "invalid child type for info";
@@ -602,6 +640,9 @@ template <> void addChild(NamespaceInfo *I, EnumInfo &&R) {
template <> void addChild(NamespaceInfo *I, TypedefInfo &&R) {
I->Children.Typedefs.emplace_back(std::move(R));
}
template <> void addChild(NamespaceInfo *I, ConceptInfo &&R) {
I->Children.Concepts.emplace_back(std::move(R));
}
// Record children:
template <> void addChild(RecordInfo *I, FunctionInfo &&R) {
@@ -651,6 +692,9 @@ template <> void addTemplate(RecordInfo *I, TemplateInfo &&P) {
template <> void addTemplate(FunctionInfo *I, TemplateInfo &&P) {
I->Template.emplace(std::move(P));
}
template <> void addTemplate(ConceptInfo *I, TemplateInfo &&P) {
I->Template = std::move(P);
}
// Template specializations go only into template records.
template <typename T>
@@ -664,6 +708,14 @@ void addTemplateSpecialization(TemplateInfo *I,
I->Specialization.emplace(std::move(TSI));
}
template <typename T> 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 <typename T>
llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) {
@@ -718,6 +770,8 @@ llvm::Error ClangDocBitcodeReader::readBlock(unsigned ID, T I) {
}
}
// TODO: Create a helper that can receive a function to reduce repetition for
// most blocks.
template <typename T>
llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
llvm::TimeTraceScope("Reducing infos", "readSubBlock");
@@ -819,6 +873,20 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
addChild(I, std::move(TI));
return llvm::Error::success();
}
case BI_CONSTRAINT_BLOCK_ID: {
ConstraintInfo CI;
if (auto Err = readBlock(ID, &CI))
return Err;
addConstraint(I, std::move(CI));
return llvm::Error::success();
}
case BI_CONCEPT_BLOCK_ID: {
ConceptInfo CI;
if (auto Err = readBlock(ID, &CI))
return Err;
addChild(I, std::move(CI));
return llvm::Error::success();
}
default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"invalid subblock type");
@@ -924,6 +992,8 @@ ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
return createInfo<EnumInfo>(ID);
case BI_TYPEDEF_BLOCK_ID:
return createInfo<TypedefInfo>(ID);
case BI_CONCEPT_BLOCK_ID:
return createInfo<ConceptInfo>(ID);
case BI_FUNCTION_BLOCK_ID:
return createInfo<FunctionInfo>(ID);
default:
@@ -964,6 +1034,7 @@ ClangDocBitcodeReader::readBitcode() {
case BI_RECORD_BLOCK_ID:
case BI_ENUM_BLOCK_ID:
case BI_TYPEDEF_BLOCK_ID:
case BI_CONCEPT_BLOCK_ID:
case BI_FUNCTION_BLOCK_ID: {
auto InfoOrErr = readBlockToInfo(ID);
if (!InfoOrErr)

View File

@@ -128,7 +128,9 @@ static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
{BI_REFERENCE_BLOCK_ID, "ReferenceBlock"},
{BI_TEMPLATE_BLOCK_ID, "TemplateBlock"},
{BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, "TemplateSpecializationBlock"},
{BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"}};
{BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"},
{BI_CONSTRAINT_BLOCK_ID, "ConstraintBlock"},
{BI_CONCEPT_BLOCK_ID, "ConceptBlock"}};
assert(Inits.size() == BlockIdCount);
for (const auto &Init : Inits)
BlockIdNameMap[Init.first] = Init.second;
@@ -205,7 +207,13 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
{TYPEDEF_USR, {"USR", &genSymbolIdAbbrev}},
{TYPEDEF_NAME, {"Name", &genStringAbbrev}},
{TYPEDEF_DEFLOCATION, {"DefLocation", &genLocationAbbrev}},
{TYPEDEF_IS_USING, {"IsUsing", &genBoolAbbrev}}};
{TYPEDEF_IS_USING, {"IsUsing", &genBoolAbbrev}},
{CONCEPT_USR, {"USR", &genSymbolIdAbbrev}},
{CONCEPT_NAME, {"Name", &genStringAbbrev}},
{CONCEPT_IS_TYPE, {"IsType", &genBoolAbbrev}},
{CONCEPT_CONSTRAINT_EXPRESSION,
{"ConstraintExpression", &genStringAbbrev}},
{CONSTRAINT_EXPRESSION, {"Expression", &genStringAbbrev}}};
assert(Inits.size() == RecordIdCount);
for (const auto &Init : Inits) {
RecordIdNameMap[Init.first] = Init.second;
@@ -263,7 +271,13 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
// Template Blocks.
{BI_TEMPLATE_BLOCK_ID, {}},
{BI_TEMPLATE_PARAM_BLOCK_ID, {TEMPLATE_PARAM_CONTENTS}},
{BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}}};
{BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}},
// Concept Block
{BI_CONCEPT_BLOCK_ID,
{CONCEPT_USR, CONCEPT_NAME, CONCEPT_IS_TYPE,
CONCEPT_CONSTRAINT_EXPRESSION}},
// Constraint Block
{BI_CONSTRAINT_BLOCK_ID, {CONSTRAINT_EXPRESSION}}};
// AbbreviationMap
@@ -524,6 +538,8 @@ void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) {
emitBlock(C);
for (const auto &C : I.Children.Typedefs)
emitBlock(C);
for (const auto &C : I.Children.Concepts)
emitBlock(C);
}
void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) {
@@ -627,12 +643,25 @@ void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) {
emitBlock(*I.Template);
}
void ClangDocBitcodeWriter::emitBlock(const ConceptInfo &I) {
StreamSubBlockGuard Block(Stream, BI_CONCEPT_BLOCK_ID);
emitRecord(I.USR, CONCEPT_USR);
emitRecord(I.Name, CONCEPT_NAME);
for (const auto &CI : I.Description)
emitBlock(CI);
emitRecord(I.IsType, CONCEPT_IS_TYPE);
emitRecord(I.ConstraintExpression, CONCEPT_CONSTRAINT_EXPRESSION);
emitBlock(I.Template);
}
void ClangDocBitcodeWriter::emitBlock(const TemplateInfo &T) {
StreamSubBlockGuard Block(Stream, BI_TEMPLATE_BLOCK_ID);
for (const auto &P : T.Params)
emitBlock(P);
if (T.Specialization)
emitBlock(*T.Specialization);
for (const auto &C : T.Constraints)
emitBlock(C);
}
void ClangDocBitcodeWriter::emitBlock(const TemplateSpecializationInfo &T) {
@@ -647,6 +676,12 @@ void ClangDocBitcodeWriter::emitBlock(const TemplateParamInfo &T) {
emitRecord(T.Contents, TEMPLATE_PARAM_CONTENTS);
}
void ClangDocBitcodeWriter::emitBlock(const ConstraintInfo &C) {
StreamSubBlockGuard Block(Stream, BI_CONSTRAINT_BLOCK_ID);
emitRecord(C.ConstraintExpr, CONSTRAINT_EXPRESSION);
emitBlock(C.ConceptRef, FieldId::F_concept);
}
bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
switch (I->IT) {
case InfoType::IT_namespace:
@@ -664,7 +699,10 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
case InfoType::IT_typedef:
emitBlock(*static_cast<clang::doc::TypedefInfo *>(I));
break;
default:
case InfoType::IT_concept:
emitBlock(*static_cast<clang::doc::ConceptInfo *>(I));
break;
case InfoType::IT_default:
llvm::errs() << "Unexpected info, unable to write.\n";
return true;
}

View File

@@ -66,7 +66,9 @@ enum BlockId {
BI_TEMPLATE_BLOCK_ID,
BI_TEMPLATE_SPECIALIZATION_BLOCK_ID,
BI_TEMPLATE_PARAM_BLOCK_ID,
BI_CONSTRAINT_BLOCK_ID,
BI_TYPEDEF_BLOCK_ID,
BI_CONCEPT_BLOCK_ID,
BI_LAST,
BI_FIRST = BI_VERSION_BLOCK_ID
};
@@ -135,6 +137,11 @@ enum RecordId {
TYPEDEF_NAME,
TYPEDEF_DEFLOCATION,
TYPEDEF_IS_USING,
CONCEPT_USR,
CONCEPT_NAME,
CONCEPT_IS_TYPE,
CONCEPT_CONSTRAINT_EXPRESSION,
CONSTRAINT_EXPRESSION,
RI_LAST,
RI_FIRST = VERSION
};
@@ -150,7 +157,8 @@ enum class FieldId {
F_vparent,
F_type,
F_child_namespace,
F_child_record
F_child_record,
F_concept
};
class ClangDocBitcodeWriter {
@@ -179,6 +187,8 @@ public:
void emitBlock(const TemplateInfo &T);
void emitBlock(const TemplateSpecializationInfo &T);
void emitBlock(const TemplateParamInfo &T);
void emitBlock(const ConceptInfo &T);
void emitBlock(const ConstraintInfo &T);
void emitBlock(const Reference &B, FieldId F);
private:

View File

@@ -985,6 +985,8 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
MainContentNodes =
genHTML(*static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle);
break;
case InfoType::IT_concept:
break;
case InfoType::IT_default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"unexpected info type");
@@ -1011,6 +1013,8 @@ static std::string getRefType(InfoType IT) {
return "enum";
case InfoType::IT_typedef:
return "typedef";
case InfoType::IT_concept:
return "concept";
}
llvm_unreachable("Unknown InfoType");
}

View File

@@ -585,6 +585,8 @@ Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
case InfoType::IT_typedef:
OS << "IT_typedef\n";
break;
case InfoType::IT_concept:
break;
case InfoType::IT_default:
return createStringError(inconvertibleErrorCode(), "unexpected InfoType");
}

View File

@@ -26,6 +26,15 @@ static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
std::optional<StringRef> RepositoryUrl);
static void serializeInfo(const EnumInfo &I, json::Object &Obj,
std::optional<StringRef> RepositoryUrl);
static void serializeInfo(const ConstraintInfo &I, Object &Obj);
// Convenience lambda to pass to serializeArray.
// If a serializeInfo needs a RepositoryUrl, create a local lambda that captures
// the optional.
static auto SerializeInfoLambda = [](const ConstraintInfo &Info,
Object &Object) {
serializeInfo(Info, Object);
};
static json::Object serializeLocation(const Location &Loc,
std::optional<StringRef> RepositoryUrl) {
@@ -248,6 +257,27 @@ static void serializeCommonChildren(const ScopeChildren &Children,
}
}
template <typename T, typename SerializationFunc>
static void serializeArray(const std::vector<T> &Records, Object &Obj,
const std::string &Key,
SerializationFunc SerializeInfo) {
json::Value RecordsArray = Array();
auto &RecordsArrayRef = *RecordsArray.getAsArray();
RecordsArrayRef.reserve(Records.size());
for (const auto &Item : Records) {
json::Value ItemVal = Object();
auto &ItemObj = *ItemVal.getAsObject();
SerializeInfo(Item, ItemObj);
RecordsArrayRef.push_back(ItemVal);
}
Obj[Key] = RecordsArray;
}
static void serializeInfo(const ConstraintInfo &I, Object &Obj) {
serializeReference(I.ConceptRef, Obj);
Obj["Expression"] = I.ConstraintExpr;
}
static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
json::Value TemplateVal = Object();
auto &TemplateObj = *TemplateVal.getAsObject();
@@ -277,9 +307,21 @@ static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
TemplateObj["Parameters"] = ParamsArray;
}
if (!Template.Constraints.empty())
serializeArray(Template.Constraints, TemplateObj, "Constraints",
SerializeInfoLambda);
Obj["Template"] = TemplateVal;
}
static void serializeInfo(const ConceptInfo &I, Object &Obj,
std::optional<StringRef> 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;
@@ -457,6 +499,10 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
Obj["Namespaces"] = NamespacesArray;
}
auto SerializeInfo = [RepositoryUrl](const auto &Info, Object &Object) {
serializeInfo(Info, Object, RepositoryUrl);
};
if (!I.Children.Functions.empty()) {
json::Value FunctionsArray = Array();
auto &FunctionsArrayRef = *FunctionsArray.getAsArray();
@@ -470,6 +516,9 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
Obj["Functions"] = FunctionsArray;
}
if (!I.Children.Concepts.empty())
serializeArray(I.Children.Concepts, Obj, "Concepts", SerializeInfo);
serializeCommonChildren(I.Children, Obj, RepositoryUrl);
}
@@ -520,6 +569,7 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
case InfoType::IT_record:
serializeInfo(*static_cast<RecordInfo *>(I), Obj, CDCtx.RepositoryUrl);
break;
case InfoType::IT_concept:
case InfoType::IT_enum:
case InfoType::IT_function:
case InfoType::IT_typedef:

View File

@@ -372,6 +372,9 @@ static llvm::Error genIndex(ClangDocContext &CDCtx) {
case InfoType::IT_typedef:
Type = "Typedef";
break;
case InfoType::IT_concept:
Type = "Concept";
break;
case InfoType::IT_default:
Type = "Other";
}
@@ -464,6 +467,8 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
case InfoType::IT_typedef:
genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
break;
case InfoType::IT_concept:
break;
case InfoType::IT_default:
return createStringError(llvm::inconvertibleErrorCode(),
"unexpected InfoType");

View File

@@ -134,6 +134,10 @@ bool MapASTVisitor::VisitTypeAliasDecl(const TypeAliasDecl *D) {
return mapDecl(D, /*isDefinition=*/true);
}
bool MapASTVisitor::VisitConceptDecl(const ConceptDecl *D) {
return mapDecl(D, true);
}
comments::FullComment *
MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const {
RawComment *Comment = Context.getRawCommentForDeclNoCache(D);

View File

@@ -41,6 +41,7 @@ public:
bool VisitFunctionDecl(const FunctionDecl *D);
bool VisitTypedefDecl(const TypedefDecl *D);
bool VisitTypeAliasDecl(const TypeAliasDecl *D);
bool VisitConceptDecl(const ConceptDecl *D);
private:
template <typename T> bool mapDecl(const T *D, bool IsDefinition);

View File

@@ -143,10 +143,13 @@ mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
return reduce<FunctionInfo>(Values);
case InfoType::IT_typedef:
return reduce<TypedefInfo>(Values);
default:
case InfoType::IT_concept:
return reduce<ConceptInfo>(Values);
case InfoType::IT_default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"unexpected info type");
}
llvm_unreachable("unhandled enumerator");
}
bool CommentInfo::operator==(const CommentInfo &Other) const {
@@ -287,6 +290,7 @@ void NamespaceInfo::merge(NamespaceInfo &&Other) {
reduceChildren(Children.Functions, std::move(Other.Children.Functions));
reduceChildren(Children.Enums, std::move(Other.Children.Enums));
reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));
reduceChildren(Children.Concepts, std::move(Other.Children.Concepts));
mergeBase(std::move(Other));
}
@@ -351,6 +355,19 @@ void TypedefInfo::merge(TypedefInfo &&Other) {
SymbolInfo::merge(std::move(Other));
}
void ConceptInfo::merge(ConceptInfo &&Other) {
assert(mergeable(Other));
if (!IsType)
IsType = Other.IsType;
if (ConstraintExpression.empty())
ConstraintExpression = std::move(Other.ConstraintExpression);
if (Template.Constraints.empty())
Template.Constraints = std::move(Other.Template.Constraints);
if (Template.Params.empty())
Template.Params = std::move(Other.Template.Params);
SymbolInfo::merge(std::move(Other));
}
BaseRecordInfo::BaseRecordInfo() : RecordInfo() {}
BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,
@@ -387,6 +404,9 @@ llvm::SmallString<16> Info::extractName() const {
case InfoType::IT_function:
return llvm::SmallString<16>("@nonymous_function_" +
toHex(llvm::toStringRef(USR)));
case InfoType::IT_concept:
return llvm::SmallString<16>("@nonymous_concept_" +
toHex(llvm::toStringRef(USR)));
case InfoType::IT_default:
return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));
}
@@ -452,6 +472,7 @@ void ScopeChildren::sort() {
llvm::sort(Functions.begin(), Functions.end());
llvm::sort(Enums.begin(), Enums.end());
llvm::sort(Typedefs.begin(), Typedefs.end());
llvm::sort(Concepts.begin(), Concepts.end());
}
} // namespace doc
} // namespace clang

View File

@@ -35,6 +35,7 @@ struct EnumInfo;
struct FunctionInfo;
struct Info;
struct TypedefInfo;
struct ConceptInfo;
enum class InfoType {
IT_default,
@@ -42,7 +43,8 @@ enum class InfoType {
IT_record,
IT_function,
IT_enum,
IT_typedef
IT_typedef,
IT_concept
};
enum class CommentKind {
@@ -166,6 +168,7 @@ struct ScopeChildren {
std::vector<FunctionInfo> Functions;
std::vector<EnumInfo> Enums;
std::vector<TypedefInfo> Typedefs;
std::vector<ConceptInfo> Concepts;
void sort();
};
@@ -211,6 +214,15 @@ struct TemplateSpecializationInfo {
std::vector<TemplateParamInfo> 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 +231,7 @@ struct TemplateInfo {
// Set when this is a specialization of another record/function.
std::optional<TemplateSpecializationInfo> Specialization;
std::vector<ConstraintInfo> Constraints;
};
// Info for field types.
@@ -513,6 +526,17 @@ struct EnumInfo : public SymbolInfo {
llvm::SmallVector<EnumValueInfo, 4> 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) {}

View File

@@ -21,6 +21,17 @@ namespace clang {
namespace doc {
namespace serialize {
namespace {
static SmallString<16> exprToString(const clang::Expr *E) {
clang::LangOptions Opts;
clang::PrintingPolicy Policy(Opts);
SmallString<16> Result;
llvm::raw_svector_ostream OS(Result);
E->printPretty(OS, nullptr, Policy);
return Result;
}
} // namespace
SymbolID hashUSR(llvm::StringRef USR) {
return llvm::SHA1::hash(arrayRefFromStringRef(USR));
}
@@ -388,9 +399,13 @@ std::string serialize(std::unique_ptr<Info> &I) {
return serialize(*static_cast<EnumInfo *>(I.get()));
case InfoType::IT_function:
return serialize(*static_cast<FunctionInfo *>(I.get()));
default:
case InfoType::IT_concept:
return serialize(*static_cast<ConceptInfo *>(I.get()));
case InfoType::IT_typedef:
case InfoType::IT_default:
return "";
}
llvm_unreachable("unhandled enumerator");
}
static void parseFullComment(const FullComment *C, CommentInfo &CI) {
@@ -489,6 +504,10 @@ static void InsertChild(ScopeChildren &Scope, TypedefInfo Info) {
Scope.Typedefs.push_back(std::move(Info));
}
static void InsertChild(ScopeChildren &Scope, ConceptInfo Info) {
Scope.Concepts.push_back(std::move(Info));
}
// Creates a parent of the correct type for the given child and inserts it into
// that parent.
//
@@ -525,9 +544,14 @@ static std::unique_ptr<Info> makeAndInsertIntoParent(ChildType Child) {
InsertChild(ParentRec->Children, std::forward<ChildType>(Child));
return ParentRec;
}
default:
llvm_unreachable("Invalid reference type for parent namespace");
case InfoType::IT_default:
case InfoType::IT_enum:
case InfoType::IT_function:
case InfoType::IT_typedef:
case InfoType::IT_concept:
break;
}
llvm_unreachable("Invalid reference type for parent namespace");
}
// There are two uses for this function.
@@ -734,6 +758,50 @@ static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C,
I.Loc.emplace_back(Loc);
}
static void
handleCompoundConstraints(const Expr *Constraint,
std::vector<ConstraintInfo> &ConstraintInfos) {
if (Constraint->getStmtClass() == Stmt::ParenExprClass) {
handleCompoundConstraints(dyn_cast<ParenExpr>(Constraint)->getSubExpr(),
ConstraintInfos);
} else if (Constraint->getStmtClass() == Stmt::BinaryOperatorClass) {
auto *BinaryOpExpr = dyn_cast<BinaryOperator>(Constraint);
handleCompoundConstraints(BinaryOpExpr->getLHS(), ConstraintInfos);
handleCompoundConstraints(BinaryOpExpr->getRHS(), ConstraintInfos);
} else if (Constraint->getStmtClass() ==
Stmt::ConceptSpecializationExprClass) {
auto *Concept = dyn_cast<ConceptSpecializationExpr>(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<AssociatedConstraint> 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<ConceptSpecializationExpr>(
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) {
@@ -745,6 +813,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 =
@@ -897,6 +967,8 @@ emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
RI->Path = getInfoRelativePath(RI->Namespace);
populateTemplateParameters(RI->Template, D);
if (RI->Template)
populateConstraints(RI->Template.value(), D->getDescribedTemplate());
// Full and partial specializations.
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D)) {
@@ -1068,6 +1140,30 @@ emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc,
return {nullptr, makeAndInsertIntoParent<EnumInfo &&>(std::move(Enum))};
}
std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
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<ConceptInfo &&>(std::move(Concept))};
}
} // namespace serialize
} // namespace doc
} // namespace clang

View File

@@ -68,6 +68,10 @@ std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
emitInfo(const TypeAliasDecl *D, const FullComment *FC, Location Loc,
bool PublicOnly);
std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc,
bool PublicOnly);
// Function to hash a given USR value for storage.
// As USRs (Unified Symbol Resolution) could be large, especially for functions
// with long type arguments, we use 160-bits SHA1(USR) values to

View File

@@ -408,6 +408,8 @@ llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
case InfoType::IT_typedef:
InfoYAML << *static_cast<clang::doc::TypedefInfo *>(I);
break;
case InfoType::IT_concept:
break;
case InfoType::IT_default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"unexpected InfoType");

View File

@@ -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>("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<FileID, unsigned> FileAndOffset =
SrcMgr.getDecomposedLoc(Decl->field_begin()->getBeginLoc());
assert(!Decl->field_empty());
auto LastField = Decl->field_begin();
while (std::next(LastField) != Decl->field_end())
++LastField;
unsigned EndOffset = SrcMgr.getFileOffset(LastField->getEndLoc());
StringRef SrcBuffer = SrcMgr.getBufferData(FileAndOffset.first);
Lexer L(SrcMgr.getLocForStartOfFile(FileAndOffset.first), LangOpts,
SrcBuffer.data(), SrcBuffer.data() + FileAndOffset.second,
SrcBuffer.data() + SrcBuffer.size());
IdentifierTable Identifiers(LangOpts);
clang::Token T;
while (!L.LexFromRawLexer(T) && L.getCurrentBufferOffset() < EndOffset) {
if (T.getKind() == tok::hash) {
L.LexFromRawLexer(T);
if (T.getKind() == tok::raw_identifier) {
clang::IdentifierInfo &II = Identifiers.get(T.getRawIdentifier());
if (II.getPPKeywordID() != clang::tok::pp_not_keyword)
return true;
}
}
}
return false;
}
static bool isSafeToRewrite(const RecordDecl *Decl, const ASTContext &Context) {
// All following checks expect at least one field declaration.
if (Decl->field_empty())
return true;
// Don't attempt to rewrite if there is a declaration like 'int a, b;'.
if (declaresMultipleFieldsInStatement(Decl))
return false;
const SourceManager &SrcMgr = Context.getSourceManager();
// Don't attempt to rewrite if a single macro expansion creates multiple
// fields.
if (declaresMultipleFieldsInMacro(Decl, SrcMgr))
return false;
// Prevent rewriting if there are preprocessor directives present between the
// start of the first field and the end of last field.
if (containsPreprocessorDirectives(Decl, SrcMgr, Context.getLangOpts()))
return false;
return true;
}
/// Calculates the new order of fields.
///
/// \returns empty vector if the list of fields doesn't match the definition.
@@ -86,6 +167,10 @@ getNewFieldsOrder(const RecordDecl *Definition,
static void
addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
std::map<std::string, tooling::Replacements> &Replacements) {
if (Old.getBegin().isMacroID())
Old = Context.getSourceManager().getExpansionRange(Old).getAsRange();
if (New.getBegin().isMacroID())
New = Context.getSourceManager().getExpansionRange(New).getAsRange();
StringRef NewText =
Lexer::getSourceText(CharSourceRange::getTokenRange(New),
Context.getSourceManager(), Context.getLangOpts());
@@ -341,6 +426,8 @@ public:
const RecordDecl *RD = findDefinition(RecordName, Context);
if (!RD)
return;
if (!isSafeToRewrite(RD, Context))
return;
SmallVector<unsigned, 4> NewFieldsOrder =
getNewFieldsOrder(RD, DesiredFieldsOrder);
if (NewFieldsOrder.empty())

View File

@@ -702,17 +702,16 @@ void NotNullTerminatedResultCheck::registerMatchers(MatchFinder *Finder) {
return hasArgument(
CC.LengthPos,
allOf(
anyOf(
ignoringImpCasts(integerLiteral().bind(WrongLengthExprName)),
allOf(unless(hasDefinition(SizeOfCharExpr)),
allOf(CC.WithIncrease
? ignoringImpCasts(hasDefinition(HasIncOp))
: ignoringImpCasts(allOf(
unless(hasDefinition(HasIncOp)),
anyOf(hasDefinition(binaryOperator().bind(
UnknownLengthName)),
hasDefinition(anything())))),
AnyOfWrongLengthInit))),
anyOf(ignoringImpCasts(integerLiteral().bind(WrongLengthExprName)),
allOf(unless(hasDefinition(SizeOfCharExpr)),
allOf(CC.WithIncrease
? ignoringImpCasts(hasDefinition(HasIncOp))
: ignoringImpCasts(
allOf(unless(hasDefinition(HasIncOp)),
hasDefinition(optionally(
binaryOperator().bind(
UnknownLengthName))))),
AnyOfWrongLengthInit))),
expr().bind(LengthExprName)));
};

View File

@@ -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<GotoStmt> Anything = anything();
Finder->addMatcher(gotoStmt(IgnoreMacros ? unless(isInMacro()) : Anything,
anyOf(unless(hasAncestor(NestedLoop)),
unless(isForwardJumping())))
.bind("goto"),
this);

View File

@@ -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

View File

@@ -33,6 +33,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule STATIC
RvalueReferenceParamNotMovedCheck.cpp
SlicingCheck.cpp
SpecialMemberFunctionsCheck.cpp
UseEnumClassCheck.cpp
VirtualClassDestructorCheck.cpp
LINK_LIBS

View File

@@ -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 @@ public:
CheckFactories.registerCheck<SlicingCheck>("cppcoreguidelines-slicing");
CheckFactories.registerCheck<modernize::UseDefaultMemberInitCheck>(
"cppcoreguidelines-use-default-member-init");
CheckFactories.registerCheck<UseEnumClassCheck>(
"cppcoreguidelines-use-enum-class");
CheckFactories.registerCheck<misc::UnconventionalAssignOperatorCheck>(
"cppcoreguidelines-c-copy-assignment-signature");
CheckFactories.registerCheck<VirtualClassDestructorCheck>(

View File

@@ -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<EnumDecl>("unscoped_enum");
diag(UnscopedEnum->getLocation(),
"enum %0 is unscoped, use 'enum class' instead")
<< UnscopedEnum;
}
} // namespace clang::tidy::cppcoreguidelines

View File

@@ -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<TraversalKind> 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

View File

@@ -24,14 +24,12 @@ void ExceptionBaseclassCheck::registerMatchers(MatchFinder *Finder) {
isSameOrDerivedFrom(hasName("::std::exception")))))))))),
// This condition is always true, but will bind to the
// template value if the thrown type is templated.
anyOf(has(expr(
hasType(substTemplateTypeParmType().bind("templ_type")))),
anything()),
optionally(has(
expr(hasType(substTemplateTypeParmType().bind("templ_type"))))),
// Bind to the declaration of the type of the value that
// is thrown. 'anything()' is necessary to always succeed
// in the 'eachOf' because builtin types are not
// 'namedDecl'.
eachOf(has(expr(hasType(namedDecl().bind("decl")))), anything()))
// is thrown. 'optionally' is necessary because builtin types
// are not 'namedDecl'.
optionally(has(expr(hasType(namedDecl().bind("decl"))))))
.bind("bad_throw"),
this);
}

View File

@@ -38,8 +38,7 @@ void StaticAssertCheck::registerMatchers(MatchFinder *Finder) {
binaryOperator(
hasAnyOperatorName("&&", "=="),
hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))),
anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)),
anything()))
optionally(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast))))
.bind("assertExprRoot"),
IsAlwaysFalse);
auto NonConstexprFunctionCall =
@@ -52,12 +51,10 @@ void StaticAssertCheck::registerMatchers(MatchFinder *Finder) {
auto NonConstexprCode =
expr(anyOf(NonConstexprFunctionCall, NonConstexprVariableReference));
auto AssertCondition =
expr(
anyOf(expr(ignoringParenCasts(anyOf(
AssertExprRoot, unaryOperator(hasUnaryOperand(
ignoringParenCasts(AssertExprRoot)))))),
anything()),
unless(NonConstexprCode), unless(hasDescendant(NonConstexprCode)))
expr(optionally(expr(ignoringParenCasts(anyOf(
AssertExprRoot, unaryOperator(hasUnaryOperand(
ignoringParenCasts(AssertExprRoot))))))),
unless(NonConstexprCode), unless(hasDescendant(NonConstexprCode)))
.bind("condition");
auto Condition =
anyOf(ignoringParenImpCasts(callExpr(

View File

@@ -26,13 +26,12 @@ void UseBoolLiteralsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
void UseBoolLiteralsCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
traverse(
TK_AsIs,
implicitCastExpr(
has(ignoringParenImpCasts(integerLiteral().bind("literal"))),
hasImplicitDestinationType(qualType(booleanType())),
unless(isInTemplateInstantiation()),
anyOf(hasParent(explicitCastExpr().bind("cast")), anything()))),
traverse(TK_AsIs,
implicitCastExpr(
has(ignoringParenImpCasts(integerLiteral().bind("literal"))),
hasImplicitDestinationType(qualType(booleanType())),
unless(isInTemplateInstantiation()),
optionally(hasParent(explicitCastExpr().bind("cast"))))),
this);
Finder->addMatcher(

View File

@@ -39,28 +39,21 @@ intCastExpression(bool IsSigned,
// std::cmp_{} functions trigger a compile-time error if either LHS or RHS
// is a non-integer type, char, enum or bool
// (unsigned char/ signed char are Ok and can be used).
const auto HasIntegerType = hasType(hasCanonicalType(qualType(
auto IntTypeExpr = expr(hasType(hasCanonicalType(qualType(
isInteger(), IsSigned ? isSignedInteger() : isUnsignedInteger(),
unless(isActualChar()), unless(booleanType()), unless(enumType()))));
const auto IntTypeExpr = expr(HasIntegerType);
unless(isActualChar()), unless(booleanType()), unless(enumType())))));
const auto ImplicitCastExpr =
CastBindName.empty() ? implicitCastExpr(hasSourceExpression(IntTypeExpr))
: implicitCastExpr(hasSourceExpression(IntTypeExpr))
.bind(CastBindName);
const auto ExplicitCastExpr =
anyOf(explicitCastExpr(has(ImplicitCastExpr)),
ignoringImpCasts(explicitCastExpr(has(ImplicitCastExpr))));
const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
// Match function calls or variable references not directly wrapped by an
// implicit cast
const auto CallIntExpr = CastBindName.empty()
? callExpr(HasIntegerType)
: callExpr(HasIntegerType).bind(CastBindName);
return expr(anyOf(ImplicitCastExpr, ExplicitCastExpr, CallIntExpr));
return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
FunctionalCastExpr));
}
static StringRef parseOpCode(BinaryOperator::Opcode Code) {

View File

@@ -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() {

View File

@@ -46,6 +46,7 @@ private:
ExprMutationAnalyzer::Memoized MutationAnalyzerCache;
utils::IncludeInserter Inserter;
const std::vector<StringRef> AllowedTypes;
bool IgnoreCoroutines;
};
} // namespace clang::tidy::performance

View File

@@ -108,6 +108,14 @@ public:
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 @@ public:
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<FunctionDecl *>(Func));
auto &FI = Visitor.Info;

View File

@@ -47,6 +47,7 @@ private:
const std::optional<unsigned> ParameterThreshold;
const std::optional<unsigned> NestingThreshold;
const std::optional<unsigned> VariableThreshold;
const bool CountMemberInitAsStmt;
static constexpr std::optional<unsigned> DefaultLineThreshold = std::nullopt;
static constexpr std::optional<unsigned> DefaultStatementThreshold = 800U;
@@ -58,6 +59,7 @@ private:
std::nullopt;
static constexpr std::optional<unsigned> DefaultVariableThreshold =
std::nullopt;
static constexpr bool DefaultCountMemberInitAsStmt = true;
};
} // namespace clang::tidy::readability

View File

@@ -348,8 +348,8 @@ void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) {
implicitCastExpr().bind("implicitCastFromBool"),
unless(hasParent(BitfieldConstruct)),
// Check also for nested casts, for example: bool -> int -> float.
anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
anything()),
optionally(
hasParent(implicitCastExpr().bind("furtherImplicitCast"))),
unless(isInTemplateInstantiation()),
unless(IsInCompilerGeneratedFunction))),
this);

View File

@@ -494,9 +494,9 @@ static std::vector<llvm::StringRef> semanticTokenModifiers() {
void ClangdLSPServer::onInitialize(const InitializeParams &Params,
Callback<llvm::json::Value> 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));

View File

@@ -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;
}

View File

@@ -528,8 +528,9 @@ struct ClientCapabilities {
/// textDocument.semanticHighlightingCapabilities.semanticHighlighting
bool TheiaSemanticHighlighting = false;
/// Supported encodings for LSP character offsets. (clangd extension).
std::optional<std::vector<OffsetEncoding>> offsetEncoding;
/// Supported encodings for LSP character offsets.
/// general.positionEncodings
std::optional<std::vector<OffsetEncoding>> PositionEncodings;
/// The content format that should be used for Hover requests.
/// textDocument.hover.contentEncoding

View File

@@ -1308,7 +1308,7 @@ getMappedRanges(ArrayRef<Range> Indexed, ArrayRef<SymbolRange> Lexed) {
return std::nullopt;
}
// Fast check for the special subset case.
if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end()))
if (llvm::includes(Indexed, Lexed))
return Lexed.vec();
std::vector<size_t> Best;

View File

@@ -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"}

View File

@@ -974,7 +974,7 @@ class Foo final {})cpp";
HI.Name = "abc";
HI.Kind = index::SymbolKind::Variable;
HI.NamespaceScope = "";
HI.Definition = "int abc = <recovery - expr>()";
HI.Definition = "int abc";
HI.Type = "int";
HI.AccessSpecifier = "public";
}},

View File

@@ -2311,6 +2311,14 @@ TEST(FindReferences, WithinAST) {
$(S::deleteObject)[[de^lete]] S;
}
};
)cpp",
// Array designators
R"cpp(
const int $def[[F^oo]] = 0;
int Bar[] = {
[$(Bar)[[F^oo]]...$(Bar)[[Fo^o]] + 1] = 0,
[$(Bar)[[^Foo]] + 2] = 1
};
)cpp"};
for (const char *Test : Tests)
checkFindRefs(Test);

View File

@@ -136,6 +136,12 @@ New checks
Finds unintended character output from ``unsigned char`` and ``signed char``
to an ``ostream``.
- New :doc:`cppcoreguidelines-use-enum-class
<clang-tidy/checks/cppcoreguidelines/use-enum-class>` check.
Finds unscoped (non-class) ``enum`` declarations and suggests using
``enum class`` instead.
- New :doc:`portability-avoid-pragma-once
<clang-tidy/checks/portability/avoid-pragma-once>` check.
@@ -191,11 +197,19 @@ Changes in existing checks
<clang-tidy/checks/concurrency/mt-unsafe>` check by fixing a false positive
where ``strerror`` was flagged as MT-unsafe.
- Improved :doc:`cppcoreguidelines-avoid-goto
<clang-tidy/checks/cppcoreguidelines/avoid-goto>` check by adding the option
`IgnoreMacros` to ignore ``goto`` labels defined in macros.
- Improved :doc:`google-readability-namespace-comments
<clang-tidy/checks/google/readability-namespace-comments>` check by adding
the option `AllowOmittingNamespaceComments` to accept if a namespace comment
is omitted entirely.
- Improved :doc:`hicpp-avoid-goto
<clang-tidy/checks/hicpp/avoid-goto>` check by adding the option
`IgnoreMacros` to ignore ``goto`` labels defined in macros.
- Improved :doc:`llvm-namespace-comment
<clang-tidy/checks/llvm/namespace-comment>` check by adding the option
`AllowOmittingNamespaceComments` to accept if a namespace comment is omitted
@@ -237,10 +251,6 @@ Changes in existing checks
<clang-tidy/checks/modernize/use-designated-initializers>` check by avoiding
diagnosing designated initializers for ``std::array`` initializations.
- Improved :doc:`modernize-use-integer-sign-comparison
<clang-tidy/checks/modernize/use-integer-sign-comparison>` check by matching
valid integer expressions not directly wrapped around an implicit cast.
- Improved :doc:`modernize-use-ranges
<clang-tidy/checks/modernize/use-ranges>` check by updating suppress
warnings logic for ``nullptr`` in ``std::find``.
@@ -269,11 +279,18 @@ Changes in existing checks
<clang-tidy/checks/performance/unnecessary-value-param>` 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
<clang-tidy/checks/readability/convert-member-functions-to-static>` check by
fixing false positives on member functions with an explicit object parameter.
- Improved :doc:`readability-function-size
<clang-tidy/checks/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
<clang-tidy/checks/readability/math-missing-parentheses>` check by fixing
false negatives where math expressions are the operand of assignment operators

View File

@@ -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`.

View File

@@ -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
<https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-class>`_
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`.

View File

@@ -212,6 +212,7 @@ Clang-Tidy Checks
:doc:`cppcoreguidelines-rvalue-reference-param-not-moved <cppcoreguidelines/rvalue-reference-param-not-moved>`,
:doc:`cppcoreguidelines-slicing <cppcoreguidelines/slicing>`,
:doc:`cppcoreguidelines-special-member-functions <cppcoreguidelines/special-member-functions>`,
:doc:`cppcoreguidelines-use-enum-class <cppcoreguidelines/use-enum-class>`,
:doc:`cppcoreguidelines-virtual-class-destructor <cppcoreguidelines/virtual-class-destructor>`, "Yes"
:doc:`darwin-avoid-spinlock <darwin/avoid-spinlock>`,
:doc:`darwin-dispatch-once-nonstatic <darwin/dispatch-once-nonstatic>`, "Yes"

View File

@@ -34,7 +34,7 @@ dependent).
.. code-block:: c++
// AFTER
enum Color : std:int8_t {
enum Color : std::int8_t {
RED = -1,
GREEN = 0,
BLUE = 1

View File

@@ -56,7 +56,7 @@ Will become:
Because the fix-it needs to change the signature of the function, it may break
builds if the function is used in multiple translation units or some codes
depends on funcion signatures.
depends on function signatures.
Options
-------
@@ -74,3 +74,10 @@ Options
default is empty. If a name in the list contains the sequence `::`, it is
matched against the qualified type name (i.e. ``namespace::Type``),
otherwise it is matched against only the type name (i.e. ``Type``).
.. option:: IgnoreCoroutines
A boolean specifying whether the check should suggest passing parameters by
reference in coroutines. Passing parameters by reference in coroutines may
not be safe, please see :doc:`cppcoreguidelines-avoid-reference-coroutine-parameters <../cppcoreguidelines/avoid-reference-coroutine-parameters>`
for more information. Default is `true`.

View File

@@ -43,3 +43,8 @@ Options
The default is `none` (ignore the number of variables).
Please note that function parameters and variables declared in lambdas,
GNU Statement Expressions, and nested class inline functions are not counted.
.. option:: CountMemberInitAsStmt
When `true`, count class member initializers in constructors as statements.
Default is `true`.

View File

@@ -329,10 +329,8 @@ bool CoverageChecker::collectFileSystemHeaders() {
else {
// Otherwise we only look at the sub-trees specified by the
// include paths.
for (std::vector<std::string>::const_iterator I = IncludePaths.begin(),
E = IncludePaths.end();
I != E; ++I) {
if (!collectFileSystemHeaders(*I))
for (const std::string &IncludePath : IncludePaths) {
if (!collectFileSystemHeaders(IncludePath))
return false;
}
}

View File

@@ -339,8 +339,8 @@ static std::string findInputFile(const CommandLineArguments &CLArgs) {
llvm::opt::Visibility VisibilityMask(options::CC1Option);
unsigned MissingArgIndex, MissingArgCount;
SmallVector<const char *, 256> Argv;
for (auto I = CLArgs.begin(), E = CLArgs.end(); I != E; ++I)
Argv.push_back(I->c_str());
for (const std::string &CLArg : CLArgs)
Argv.push_back(CLArg.c_str());
InputArgList Args = getDriverOptTable().ParseArgs(
Argv, MissingArgIndex, MissingArgCount, VisibilityMask);
std::vector<std::string> Inputs = Args.getAllArgValues(OPT_INPUT);

View File

@@ -69,8 +69,7 @@ ModularizeUtilities *ModularizeUtilities::createModularizeUtilities(
// Load all header lists and dependencies.
std::error_code ModularizeUtilities::loadAllHeaderListsAndDependencies() {
// For each input file.
for (auto I = InputFilePaths.begin(), E = InputFilePaths.end(); I != E; ++I) {
llvm::StringRef InputPath = *I;
for (llvm::StringRef InputPath : InputFilePaths) {
// If it's a module map.
if (InputPath.ends_with(".modulemap")) {
// Load the module map.

View File

@@ -0,0 +1,34 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b };
};
template<typename T>
requires Addable<T>
struct MyClass;
// CHECK: "Name": "MyClass",
// CHECK-NEXT: "Namespace": [
// CHECK-NEXT: "GlobalNamespace"
// CHECK-NEXT: ],
// CHECK-NEXT: "Path": "GlobalNamespace",
// CHECK-NEXT: "TagType": "struct",
// CHECK-NEXT: "Template": {
// CHECK-NEXT: "Constraints": [
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Addable<T>",
// CHECK-NEXT: "Name": "Addable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Addable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "Parameters": [
// CHECK-NEXT: "typename T"
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"

View File

@@ -0,0 +1,121 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
// RUN: FileCheck %s < %t/GlobalNamespace/index.json
template<typename T> concept Incrementable = requires (T a) {
a++;
};
template<typename T> concept Decrementable = requires (T a) {
a--;
};
template<typename T> concept PreIncrementable = requires (T a) {
++a;
};
template<typename T> concept PreDecrementable = requires (T a) {
--a;
};
template<typename T> requires Incrementable<T> && Decrementable<T> void One();
template<typename T> requires (Incrementable<T> && Decrementable<T>) void Two();
template<typename T> requires (Incrementable<T> && Decrementable<T>) || (PreIncrementable<T> && PreDecrementable<T>) void Three();
template<typename T> requires (Incrementable<T> && Decrementable<T>) || PreIncrementable<T> void Four();
// CHECK: "Name": "One",
// CHECK: "Template": {
// CHECK-NEXT: "Constraints": [
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Incrementable<T>",
// CHECK-NEXT: "Name": "Incrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Incrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Decrementable<T>",
// CHECK-NEXT: "Name": "Decrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Decrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK: "Name": "Two",
// CHECK: "Template": {
// CHECK-NEXT: "Constraints": [
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Incrementable<T>",
// CHECK-NEXT: "Name": "Incrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Incrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Decrementable<T>",
// CHECK-NEXT: "Name": "Decrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Decrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK: "Name": "Three",
// CHECK: "Template": {
// CHECK-NEXT: "Constraints": [
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Incrementable<T>",
// CHECK-NEXT: "Name": "Incrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Incrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Decrementable<T>",
// CHECK-NEXT: "Name": "Decrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Decrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "PreIncrementable<T>",
// CHECK-NEXT: "Name": "PreIncrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "PreIncrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "PreDecrementable<T>",
// CHECK-NEXT: "Name": "PreDecrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "PreDecrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK: "Name": "Four",
// CHECK: "Template": {
// CHECK-NEXT: "Constraints": [
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Incrementable<T>",
// CHECK-NEXT: "Name": "Incrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Incrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Decrementable<T>",
// CHECK-NEXT: "Name": "Decrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Decrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "PreIncrementable<T>",
// CHECK-NEXT: "Name": "PreIncrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "PreIncrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],

View File

@@ -0,0 +1,38 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
// RUN: FileCheck %s < %t/GlobalNamespace/index.json
// Requires that T suports post and pre-incrementing.
template<typename T>
concept Incrementable = requires(T x) {
++x;
x++;
};
// CHECK: {
// CHECK-NEXT: "Concepts": [
// CHECK-NEXT: {
// CHECK-NEXT: "ConstraintExpression": "requires (T x) { ++x; x++; }",
// CHECK-NEXT: "Description": [
// CHECK-NEXT: {
// CHECK-NEXT: "FullComment": {
// CHECK-NEXT: "Children": [
// CHECK-NEXT: {
// CHECK-NEXT: "ParagraphComment": {
// CHECK-NEXT: "Children": [
// CHECK-NEXT: {
// CHECK-NEXT: "TextComment": " Requires that T suports post and pre-incrementing."
// CHECK: ],
// CHECK-NEXT: "IsType": true,
// CHECK-NEXT: "Name": "Incrementable",
// CHECK-NEXT: "Template": {
// CHECK-NEXT: "Parameters": [
// CHECK-NEXT: "typename T"
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK: "Name": "",
// CHECK: "USR": "0000000000000000000000000000000000000000"
// CHECK: }

View File

@@ -0,0 +1,79 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: clang-doc --extra-arg -std=c++20 --output=%t --format=json --executor=standalone %s
// RUN: FileCheck %s < %t/GlobalNamespace/index.json
template<typename T>
concept Incrementable = requires(T x) {
++x;
x++;
};
template<typename T> void increment(T t) requires Incrementable<T>;
template<Incrementable T> Incrementable auto incrementTwo(T t);
// CHECK: "Functions": [
// CHECK-NEXT: {
// CHECK-NEXT: "IsStatic": false,
// CHECK-NEXT: "Name": "increment",
// CHECK-NEXT: "Params": [
// CHECK-NEXT: {
// CHECK-NEXT: "Name": "t",
// CHECK-NEXT: "Type": "T"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "ReturnType": {
// CHECK-NEXT: "IsBuiltIn": false,
// CHECK-NEXT: "IsTemplate": false,
// CHECK-NEXT: "Name": "void",
// CHECK-NEXT: "QualName": "void",
// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
// CHECK-NEXT: },
// CHECK-NEXT: "Template": {
// CHECK-NEXT: "Constraints": [
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Incrementable<T>",
// CHECK-NEXT: "Name": "Incrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Incrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "Parameters": [
// CHECK-NEXT: "typename T"
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "IsStatic": false,
// CHECK-NEXT: "Name": "incrementTwo",
// CHECK-NEXT: "Params": [
// CHECK-NEXT: {
// CHECK-NEXT: "Name": "t",
// CHECK-NEXT: "Type": "T"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "ReturnType": {
// CHECK-NEXT: "IsBuiltIn": false,
// CHECK-NEXT: "IsTemplate": false,
// CHECK-NEXT: "Name": "Incrementable auto",
// CHECK-NEXT: "QualName": "Incrementable auto",
// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
// CHECK-NEXT: },
// CHECK-NEXT: "Template": {
// CHECK-NEXT: "Constraints": [
// CHECK-NEXT: {
// CHECK-NEXT: "Expression": "Incrementable<T>",
// CHECK-NEXT: "Name": "Incrementable",
// CHECK-NEXT: "Path": "",
// CHECK-NEXT: "QualName": "Incrementable",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "Parameters": [
// CHECK-NEXT: "Incrementable T"
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }

View File

@@ -103,5 +103,23 @@ typedef int MyTypedef;
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
// CHECK-NOT: "Variables": [
// CHECK-NOT: "Variables": [
// CHECK-NOT: {
// CHECK-NOT: "IsStatic": true,
// CHECK-NOT: "Location": {
// CHECK-NOT: "Filename": "{{.*}}namespace.cpp",
// CHECK-NOT: "LineNumber": 13
// CHECK-NOT: },
// CHECK-NOT: "Name": "Global",
// CHECK-NOT: "Type": {
// COM: FIXME: IsBuiltIn emits as its default value
// CHECK-NOT: "IsBuiltIn": false,
// CHECK-NOT: "IsTemplate": false,
// CHECK-NOT: "Name": "int",
// CHECK-NOT: "QualName": "int",
// CHECK-NOT: "USR": "0000000000000000000000000000000000000000"
// CHECK-NOT: },
// CHECK-NOT: "USR": "{{[0-9A-F]*}}"
// CHECK-NOT: }
// CHECK-NOT: ]
// CHECK-NEXT: }

View File

@@ -0,0 +1,13 @@
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
namespace bar {
#define FIELDS_DECL int x; int y; // CHECK: {{^#define FIELDS_DECL int x; int y;}}
// The order of fields should not change.
struct Foo {
FIELDS_DECL // CHECK: {{^ FIELDS_DECL}}
int z; // CHECK-NEXT: {{^ int z;}}
};
} // end namespace bar

View File

@@ -0,0 +1,24 @@
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
namespace bar {
#define INT_DECL(NAME) int NAME // CHECK: {{^#define INT_DECL\(NAME\) int NAME}}
#define MACRO_DECL int x; // CHECK-NEXT: {{^#define MACRO_DECL int x;}}
struct Foo {
MACRO_DECL // CHECK: {{^ INT_DECL\(z\);}}
int y; // CHECK-NEXT: {{^ int y;}}
INT_DECL(z); // CHECK-NEXT: {{^ MACRO_DECL}}
};
#define FOO 0 // CHECK: {{^#define FOO 0}}
#define BAR 1 // CHECK-NEXT: {{^#define BAR 1}}
#define BAZ 2 // CHECK-NEXT: {{^#define BAZ 2}}
struct Foo foo = {
FOO, // CHECK: {{^ BAZ,}}
BAR, // CHECK-NEXT: {{^ BAR,}}
BAZ, // CHECK-NEXT: {{^ FOO,}}
};
} // end namespace bar

View File

@@ -0,0 +1,11 @@
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
namespace bar {
// The order of fields should not change.
struct Foo {
int x, y; // CHECK: {{^ int x, y;}}
double z; // CHECK-NEXT: {{^ double z;}}
};
} // end namespace bar

View File

@@ -0,0 +1,15 @@
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s
namespace bar {
#define DEFINE_FOO
// This is okay to reorder.
#ifdef DEFINE_FOO
struct Foo {
int x; // CHECK: {{^ int y;}}
int y; // CHECK-NEXT: {{^ int x;}}
};
#endif
} // end namespace bar

View File

@@ -0,0 +1,15 @@
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order y,x %s -- | FileCheck %s
namespace bar {
#define DEFINE_FIELDS
// This is okay to reorder.
struct Foo {
#ifdef DEFINE_FIELDS // CHECK: {{^#ifdef DEFINE_FIELDS}}
int x; // CHECK-NEXT: {{^ int y;}}
int y; // CHECK-NEXT: {{^ int x;}}
#endif // CHECK-NEXT: {{^#endif}}
};
} // end namespace bar

View File

@@ -0,0 +1,16 @@
// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,y,x %s -- | FileCheck %s
namespace bar {
#define ADD_Z
// The order of fields should not change.
struct Foo {
int x; // CHECK: {{^ int x;}}
int y; // CHECK-NEXT: {{^ int y;}}
#ifdef ADD_Z // CHECK-NEXT: {{^#ifdef ADD_Z}}
int z; // CHECK-NEXT: {{^ int z;}}
#endif // CHECK-NEXT: {{^#endif}}
};
} // end namespace bar

View File

@@ -1,12 +1,13 @@
// RUN: %check_clang_tidy %s cppcoreguidelines-avoid-goto %t
// RUN: %check_clang_tidy -check-suffix=,MACRO %s cppcoreguidelines-avoid-goto %t
// RUN: %check_clang_tidy %s cppcoreguidelines-avoid-goto %t -- -config="{CheckOptions: { cppcoreguidelines-avoid-goto.IgnoreMacros: true }}"
void noop() {}
int main() {
noop();
goto jump_to_me;
// CHECK-NOTES: [[@LINE-1]]:3: warning: avoid using 'goto' for flow control
// CHECK-NOTES: [[@LINE+3]]:1: note: label defined here
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: avoid using 'goto' for flow control
// CHECK-MESSAGES: [[@LINE+3]]:1: note: label defined here
noop();
jump_to_me:;
@@ -14,14 +15,14 @@ jump_to_me:;
jump_backwards:;
noop();
goto jump_backwards;
// CHECK-NOTES: [[@LINE-1]]:3: warning: avoid using 'goto' for flow control
// CHECK-NOTES: [[@LINE-4]]:1: note: label defined here
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: avoid using 'goto' for flow control
// CHECK-MESSAGES: [[@LINE-4]]:1: note: label defined here
goto jump_in_line;
;
jump_in_line:;
// CHECK-NOTES: [[@LINE-3]]:3: warning: avoid using 'goto' for flow control
// CHECK-NOTES: [[@LINE-2]]:1: note: label defined here
// CHECK-MESSAGES: [[@LINE-3]]:3: warning: avoid using 'goto' for flow control
// CHECK-MESSAGES: [[@LINE-2]]:1: note: label defined here
// Test the GNU extension https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
some_label:;
@@ -132,8 +133,41 @@ before_the_loop:
for (int j = 0; j < 10; ++j) {
if (i * j > 80)
goto before_the_loop;
// CHECK-NOTES: [[@LINE-1]]:9: warning: avoid using 'goto' for flow control
// CHECK-NOTES: [[@LINE-8]]:1: note: label defined here
// CHECK-MESSAGES: [[@LINE-1]]:9: warning: avoid using 'goto' for flow control
// CHECK-MESSAGES: [[@LINE-8]]:1: note: label defined here
}
}
}
#define macro_goto_code \
noop(); \
goto jump_to_me; \
noop(); \
jump_to_me:; \
#define macro_goto_label jump_to_me:;
#define macro_goto_jump goto jump_to_me;
void inside_macro_all() {
macro_goto_code
// CHECK-MESSAGES-MACRO: [[@LINE-1]]:3: warning: avoid using 'goto' for flow control
// CHECK-MESSAGES-MACRO: [[@LINE-2]]:3: note: label defined here
}
void inside_macro_label() {
noop();
goto jump_to_me;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: avoid using 'goto' for flow control
// CHECK-MESSAGES: [[@LINE+2]]:3: note: label defined here
noop();
macro_goto_label
}
void inside_macro_goto() {
noop();
macro_goto_jump
// CHECK-MESSAGES-MACRO: [[@LINE-1]]:3: warning: avoid using 'goto' for flow control
// CHECK-MESSAGES-MACRO: [[@LINE+2]]:3: note: label defined here
noop();
jump_to_me:;
}

View File

@@ -0,0 +1,62 @@
// RUN: %check_clang_tidy -std=c++11-or-later -check-suffix=ALL,DEFAULT %s \
// RUN: cppcoreguidelines-use-enum-class %t --
// RUN: %check_clang_tidy -std=c++11-or-later -check-suffix=ALL %s \
// RUN: cppcoreguidelines-use-enum-class %t -- \
// RUN: -config="{CheckOptions: { \
// RUN: cppcoreguidelines-use-enum-class.IgnoreUnscopedEnumsInClasses: true \
// RUN: }}" --
enum E {};
// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'E' is unscoped, use 'enum class' instead
enum class EC {};
enum struct ES {};
struct S {
enum E {};
// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead
enum class EC {};
};
class C {
enum E {};
// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead
enum class EC {};
};
template<class T>
class TC {
enum E {};
// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead
enum class EC {};
};
union U {
enum E {};
// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: enum 'E' is unscoped, use 'enum class' instead
enum class EC {};
};
namespace {
enum E {};
// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'E' is unscoped, use 'enum class' instead
enum class EC {};
} // namespace
namespace N {
enum E {};
// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'E' is unscoped, use 'enum class' instead
enum class EC {};
} // namespace N
template<enum ::EC>
static void foo();
enum ForwardE : int;
// CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: enum 'ForwardE' is unscoped, use 'enum class' instead
enum class ForwardEC : int;
enum struct ForwardES : int;

View File

@@ -121,81 +121,3 @@ int AllComparisons() {
return 0;
}
namespace PR127471 {
int getSignedValue();
unsigned int getUnsignedValue();
void callExprTest() {
if (getSignedValue() < getUnsignedValue())
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(getSignedValue() , getUnsignedValue()))
int sVar = 0;
if (getUnsignedValue() > sVar)
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_greater(getUnsignedValue() , sVar))
unsigned int uVar = 0;
if (getSignedValue() > uVar)
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_greater(getSignedValue() , uVar))
}
// Add a class with member functions for testing member function calls
class TestClass {
public:
int getSignedValue() { return -5; }
unsigned int getUnsignedValue() { return 5; }
};
void memberFunctionTests() {
TestClass obj;
if (obj.getSignedValue() < obj.getUnsignedValue())
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(obj.getSignedValue() , obj.getUnsignedValue()))
}
void castFunctionTests() {
// C-style casts with function calls
if ((int)getUnsignedValue() < (unsigned int)getSignedValue())
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(getUnsignedValue(),getSignedValue()))
// Static casts with function calls
if (static_cast<int>(getUnsignedValue()) < static_cast<unsigned int>(getSignedValue()))
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(getUnsignedValue(),getSignedValue()))
}
// Define tests
#define SIGNED_FUNC getSignedValue()
#define UNSIGNED_FUNC getUnsignedValue()
void defineTests() {
if (SIGNED_FUNC < UNSIGNED_FUNC)
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(SIGNED_FUNC , UNSIGNED_FUNC))
}
// Template tests (should not warn)
template <typename T1>
void templateFunctionTest(T1 value) {
if (value() < getUnsignedValue())
return;
if (value() < (getSignedValue() || getUnsignedValue()))
return;
}
} // namespace PR127471

View File

@@ -0,0 +1,65 @@
// RUN: %check_clang_tidy -std=c++20-or-later %s performance-unnecessary-value-param %t -- -fix-errors
// RUN: %check_clang_tidy -std=c++20-or-later %s performance-unnecessary-value-param %t -- \
// RUN: -config='{CheckOptions: {performance-unnecessary-value-param.IgnoreCoroutines: true}}' -fix-errors
// RUN: %check_clang_tidy -check-suffix=ALLOWED -std=c++20-or-later %s performance-unnecessary-value-param %t -- \
// RUN: -config='{CheckOptions: {performance-unnecessary-value-param.IgnoreCoroutines: false}}' -fix-errors
namespace std {
template <class Ret, typename... T> struct coroutine_traits {
using promise_type = typename Ret::promise_type;
};
template <class Promise = void> struct coroutine_handle {
static coroutine_handle from_address(void *) noexcept;
static coroutine_handle from_promise(Promise &promise);
constexpr void *address() const noexcept;
};
template <> struct coroutine_handle<void> {
template <class PromiseType>
coroutine_handle(coroutine_handle<PromiseType>) noexcept;
static coroutine_handle from_address(void *);
constexpr void *address() const noexcept;
};
struct suspend_always {
bool await_ready() noexcept { return false; }
void await_suspend(coroutine_handle<>) noexcept {}
void await_resume() noexcept {}
};
struct suspend_never {
bool await_ready() noexcept { return true; }
void await_suspend(coroutine_handle<>) noexcept {}
void await_resume() noexcept {}
};
} // namespace std
struct ReturnObject {
struct promise_type {
ReturnObject get_return_object() { return {}; }
ReturnObject return_void() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {}
std::suspend_always yield_value(int value) { return {}; }
};
};
struct A {
A(const A&);
};
ReturnObject foo_coroutine(const A a) {
// CHECK-MESSAGES-ALLOWED: [[@LINE-1]]:36: warning: the const qualified parameter 'a'
// CHECK-FIXES: ReturnObject foo_coroutine(const A a) {
co_return;
}
ReturnObject foo_not_coroutine(const A a) {
// CHECK-MESSAGES: [[@LINE-1]]:40: warning: the const qualified parameter 'a'
// CHECK-MESSAGES-ALLOWED: [[@LINE-2]]:40: warning: the const qualified parameter 'a'
return ReturnObject{};
}

View File

@@ -0,0 +1,73 @@
// RUN: %check_clang_tidy %s readability-function-size %t -- \
// RUN: -config='{CheckOptions: { \
// RUN: readability-function-size.LineThreshold: 0, \
// RUN: readability-function-size.StatementThreshold: 0, \
// RUN: readability-function-size.BranchThreshold: 0, \
// RUN: readability-function-size.ParameterThreshold: 5, \
// RUN: readability-function-size.NestingThreshold: 2, \
// RUN: readability-function-size.VariableThreshold: 1, \
// RUN: readability-function-size.CountMemberInitAsStmt: false \
// RUN: }}'
// Bad formatting is intentional, don't run clang-format over the whole file!
void foo1() {
}
void foo2() {;}
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'foo2' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-2]]:6: note: 1 statements (threshold 0)
struct A {
A(int c, int d) : a(0), b(c) { ; }
int a;
int b;
};
// CHECK-MESSAGES: :[[@LINE-4]]:3: warning: function 'A' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-5]]:3: note: 1 statements (threshold 0)
struct B {
B(int x, int y, int z) : a(x + y * z), b(), c_a(y, z) {
;
}
int a;
int b;
A c_a;
};
// CHECK-MESSAGES: :[[@LINE-7]]:3: warning: function 'B' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 2 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-9]]:3: note: 1 statements (threshold 0)
struct C : A, B {
// 0 statements
C() : A(0, 4), B(1, 2, 3) {}
};
template<typename T>
struct TemplateC {
// 0 statements
TemplateC() : a(3) {}
T a;
};
template<typename T>
struct TemplateD {
template<typename U>
TemplateD(U&& val) : member(val) {
;
}
T member;
};
// CHECK-MESSAGES: :[[@LINE-6]]:3: warning: function 'TemplateD<T>' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-7]]:3: note: 2 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 1 statements (threshold 0)
void instantiate() {
TemplateC<int> c;
TemplateD<int> d(5);
}
// CHECK-MESSAGES: :[[@LINE-4]]:6: warning: function 'instantiate' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-5]]:6: note: 3 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-6]]:6: note: 2 statements (threshold 0)
// CHECK-MESSAGES: :[[@LINE-7]]:6: note: 2 variables (threshold 1)

View File

@@ -319,3 +319,60 @@ void variables_16() {
// CHECK-MESSAGES: :[[@LINE-5]]:6: note: 3 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-6]]:6: note: 4 statements (threshold 0)
// CHECK-MESSAGES: :[[@LINE-7]]:6: note: 2 variables (threshold 1)
struct A {
A(int c, int d) : a(0), b(c) { ; }
int a;
int b;
};
// CHECK-MESSAGES: :[[@LINE-4]]:3: warning: function 'A' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-5]]:3: note: 3 statements (threshold 0)
struct B {
B(int x, int y, int z) : a(x + y * z), b(), c_a(y, z) {
;
}
int a;
int b;
A c_a;
};
// CHECK-MESSAGES: :[[@LINE-7]]:3: warning: function 'B' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 2 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-9]]:3: note: 4 statements (threshold 0)
struct C : A, B {
C() : A(0, 4), B(1, 2, 3) {}
};
// CHECK-MESSAGES: :[[@LINE-2]]:3: warning: function 'C' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-3]]:3: note: 2 statements (threshold 0)
template<typename T>
struct TemplateC {
// 0 statements
TemplateC() : a(3) {}
T a;
};
// CHECK-MESSAGES: :[[@LINE-3]]:3: warning: function 'TemplateC<T>' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-4]]:3: note: 1 statements (threshold 0)
template<typename T>
struct TemplateD {
template<typename U>
TemplateD(U&& val) : member(val) {
;
}
T member;
};
// CHECK-MESSAGES: :[[@LINE-6]]:3: warning: function 'TemplateD<T>' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-7]]:3: note: 2 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-8]]:3: note: 2 statements (threshold 0)
void instantiate() {
TemplateC<int> c;
TemplateD<int> d(5);
}
// CHECK-MESSAGES: :[[@LINE-4]]:6: warning: function 'instantiate' exceeds recommended size/complexity thresholds [readability-function-size]
// CHECK-MESSAGES: :[[@LINE-5]]:6: note: 3 lines including whitespace and comments (threshold 0)
// CHECK-MESSAGES: :[[@LINE-6]]:6: note: 2 statements (threshold 0)
// CHECK-MESSAGES: :[[@LINE-7]]:6: note: 2 variables (threshold 1)

Some files were not shown because too many files have changed in this diff Show More