Merge branch 'main' into p2996
This commit is contained in:
@@ -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)
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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
|
||||
@@ -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}")
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
4
.github/new-prs-labeler.yml
vendored
4
.github/new-prs-labeler.yml
vendored
@@ -777,6 +777,10 @@ backend:NVPTX:
|
||||
- 'llvm/**/*nvptx*/**'
|
||||
- 'llvm/**/*NVPTX*/**'
|
||||
|
||||
backend:MIPS:
|
||||
- '**/*mips*'
|
||||
- '**/*Mips*'
|
||||
|
||||
backend:RISC-V:
|
||||
- clang/**/*riscv*
|
||||
- clang/**/*RISCV*
|
||||
|
||||
8
.github/workflows/libcxx-build-and-test.yaml
vendored
8
.github/workflows/libcxx-build-and-test.yaml
vendored
@@ -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'
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
3
.github/workflows/premerge.yaml
vendored
3
.github/workflows/premerge.yaml
vendored
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}"))
|
||||
|
||||
12
bolt/test/perf2bolt/AArch64/perf2bolt-spe.test
Normal file
12
bolt/test/perf2bolt/AArch64/perf2bolt-spe.test
Normal 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
|
||||
|
||||
9
bolt/test/perf2bolt/X86/perf2bolt-spe.test
Normal file
9
bolt/test/perf2bolt/X86/perf2bolt-spe.test
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
164
bolt/unittests/Profile/PerfSpeEvents.cpp
Normal file
164
bolt/unittests/Profile/PerfSpeEvents.cpp
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)));
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,6 +33,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule STATIC
|
||||
RvalueReferenceParamNotMovedCheck.cpp
|
||||
SlicingCheck.cpp
|
||||
SpecialMemberFunctionsCheck.cpp
|
||||
UseEnumClassCheck.cpp
|
||||
VirtualClassDestructorCheck.cpp
|
||||
|
||||
LINK_LIBS
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -46,6 +46,7 @@ private:
|
||||
ExprMutationAnalyzer::Memoized MutationAnalyzerCache;
|
||||
utils::IncludeInserter Inserter;
|
||||
const std::vector<StringRef> AllowedTypes;
|
||||
bool IgnoreCoroutines;
|
||||
};
|
||||
|
||||
} // namespace clang::tidy::performance
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
32
clang-tools-extra/clangd/test/positionencoding.test
Normal file
32
clang-tools-extra/clangd/test/positionencoding.test
Normal 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"}
|
||||
@@ -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";
|
||||
}},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
34
clang-tools-extra/test/clang-doc/json/class-requires.cpp
Normal file
34
clang-tools-extra/test/clang-doc/json/class-requires.cpp
Normal 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]*}}"
|
||||
121
clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
Normal file
121
clang-tools-extra/test/clang-doc/json/compound-constraints.cpp
Normal 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: ],
|
||||
38
clang-tools-extra/test/clang-doc/json/concept.cpp
Normal file
38
clang-tools-extra/test/clang-doc/json/concept.cpp
Normal 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: }
|
||||
79
clang-tools-extra/test/clang-doc/json/function-requires.cpp
Normal file
79
clang-tools-extra/test/clang-doc/json/function-requires.cpp
Normal 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: }
|
||||
@@ -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: }
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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{};
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
Reference in New Issue
Block a user