Fix windows build failure and clang-format
Created using spr 1.3.6
This commit is contained in:
commit
9b7be34863
@ -24,6 +24,7 @@ function at-exit {
|
||||
retcode=$?
|
||||
|
||||
mkdir -p artifacts
|
||||
sccache --show-stats
|
||||
sccache --show-stats >> artifacts/sccache_stats.txt
|
||||
cp "${BUILD_DIR}"/.ninja_log artifacts/.ninja_log
|
||||
cp "${MONOREPO_ROOT}"/*.log artifacts/ || :
|
||||
|
||||
9
.github/new-prs-labeler.yml
vendored
9
.github/new-prs-labeler.yml
vendored
@ -90,9 +90,6 @@ LTO:
|
||||
- llvm/lib/Transforms/*/FunctionImport*
|
||||
- llvm/tools/gold/**
|
||||
|
||||
mc:
|
||||
- llvm/*/MC/**
|
||||
|
||||
clang:driver:
|
||||
- clang/*/Driver/**
|
||||
|
||||
@ -621,6 +618,12 @@ llvm:adt:
|
||||
llvm:support:
|
||||
- llvm/**/Support/**
|
||||
|
||||
# Skip llvm/test/MC and llvm/unittests/MC, which includes target-specific directories.
|
||||
llvm:mc:
|
||||
- llvm/include/llvm/MC/**
|
||||
- llvm/lib/MC/**
|
||||
- llvm/tools/llvm-mc/**
|
||||
|
||||
llvm:transforms:
|
||||
- llvm/lib/Transforms/**
|
||||
- llvm/include/llvm/Transforms/**
|
||||
|
||||
@ -81,6 +81,8 @@ RUN curl -L 'https://github.com/mozilla/sccache/releases/download/v0.10.0/sccach
|
||||
|
||||
ENV LLVM_SYSROOT=$LLVM_SYSROOT
|
||||
ENV PATH=${LLVM_SYSROOT}/bin:${PATH}
|
||||
ENV CC=clang
|
||||
ENV CXX=clang++
|
||||
|
||||
# Create a new user to avoid test failures related to a lack of expected
|
||||
# permissions issues in some tests. Set the user id to 1001 as that is the
|
||||
|
||||
38
.github/workflows/libclang-python-tests.yml
vendored
38
.github/workflows/libclang-python-tests.yml
vendored
@ -4,7 +4,6 @@ permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
@ -13,29 +12,46 @@ on:
|
||||
- 'clang/tools/libclang/**'
|
||||
- 'clang/CMakeList.txt'
|
||||
- '.github/workflows/libclang-python-tests.yml'
|
||||
- '.github/workflows/llvm-project-tests.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clang/bindings/python/**'
|
||||
- 'clang/tools/libclang/**'
|
||||
- 'clang/CMakeList.txt'
|
||||
- '.github/workflows/libclang-python-tests.yml'
|
||||
- '.github/workflows/llvm-project-tests.yml'
|
||||
|
||||
jobs:
|
||||
check-clang-python:
|
||||
# Build libclang and then run the libclang Python binding's unit tests.
|
||||
# There is an issue running on "windows-2019".
|
||||
# See https://github.com/llvm/llvm-project/issues/76601#issuecomment-1873049082.
|
||||
name: Build and run Python unit tests
|
||||
if: github.repository == 'llvm/llvm-project'
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.8", "3.13"]
|
||||
uses: ./.github/workflows/llvm-project-tests.yml
|
||||
with:
|
||||
build_target: check-clang-python
|
||||
projects: clang
|
||||
# There is an issue running on "windows-2019".
|
||||
# See https://github.com/llvm/llvm-project/issues/76601#issuecomment-1873049082.
|
||||
os_list: '["ubuntu-24.04"]'
|
||||
python_version: ${{ matrix.python-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Setup ccache
|
||||
uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17
|
||||
with:
|
||||
max-size: 2G
|
||||
key: spirv-ubuntu-24.04
|
||||
variant: sccache
|
||||
- name: Build and Test
|
||||
run: |
|
||||
mkdir build
|
||||
cmake -GNinja \
|
||||
-S llvm \
|
||||
-B build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_ENABLE_ASSERTIONS=ON \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||
-DLLVM_ENABLE_PROJECTS=clang
|
||||
ninja -C build check-clang-python
|
||||
|
||||
31
.github/workflows/mlir-spirv-tests.yml
vendored
31
.github/workflows/mlir-spirv-tests.yml
vendored
@ -24,9 +24,28 @@ jobs:
|
||||
check_spirv:
|
||||
if: github.repository_owner == 'llvm'
|
||||
name: Test MLIR SPIR-V
|
||||
uses: ./.github/workflows/llvm-project-tests.yml
|
||||
with:
|
||||
build_target: check-mlir
|
||||
projects: mlir
|
||||
extra_cmake_args: '-DLLVM_TARGETS_TO_BUILD="host" -DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON'
|
||||
os_list: '["ubuntu-24.04"]'
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: ghcr.io/llvm/ci-ubuntu-24.04:latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup ccache
|
||||
uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17
|
||||
with:
|
||||
max-size: 2G
|
||||
key: spirv-mlir-ubuntu-24.04
|
||||
variant: sccache
|
||||
- name: Build and Test
|
||||
run: |
|
||||
mkdir build
|
||||
cmake -GNinja \
|
||||
-S llvm \
|
||||
-B build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_ENABLE_ASSERTIONS=ON \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||
-DLLVM_TARGETS_TO_BUILD="host" \
|
||||
-DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON \
|
||||
-DLLVM_ENABLE_PROJECTS=mlir
|
||||
ninja -C build check-mlir
|
||||
|
||||
31
.github/workflows/spirv-tests.yml
vendored
31
.github/workflows/spirv-tests.yml
vendored
@ -4,7 +4,6 @@ permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'llvm/lib/Target/SPIRV/**'
|
||||
@ -21,9 +20,27 @@ jobs:
|
||||
check_spirv:
|
||||
if: github.repository_owner == 'llvm'
|
||||
name: Test SPIR-V
|
||||
uses: ./.github/workflows/llvm-project-tests.yml
|
||||
with:
|
||||
build_target: check-llvm-codegen-spirv
|
||||
projects:
|
||||
extra_cmake_args: '-DLLVM_TARGETS_TO_BUILD="SPIRV" -DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON'
|
||||
os_list: '["ubuntu-24.04"]'
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: ghcr.io/llvm/ci-ubuntu-24.04:latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup ccache
|
||||
uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17
|
||||
with:
|
||||
max-size: 2G
|
||||
key: spirv-ubuntu-24.04
|
||||
variant: sccache
|
||||
- name: Build and Test
|
||||
run: |
|
||||
mkdir build
|
||||
cmake -GNinja \
|
||||
-S llvm \
|
||||
-B build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_ENABLE_ASSERTIONS=ON \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
|
||||
-DLLVM_TARGETS_TO_BUILD="SPIRV" \
|
||||
-DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON
|
||||
ninja -C build check-llvm-codegen-spirv
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -52,6 +52,11 @@ autoconf/autom4te.cache
|
||||
# CLion project configuration
|
||||
/.idea
|
||||
/cmake-build*
|
||||
# Coding assistants' stuff
|
||||
/CLAUDE.md
|
||||
/.claude/
|
||||
/GEMINI.md
|
||||
/.gemini/
|
||||
|
||||
#==============================================================================#
|
||||
# Directories to ignore (do not add trailing '/'s, they skip symlinks).
|
||||
|
||||
@ -740,6 +740,10 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Return true if the hlt instruction under the x86, otherwise, default to
|
||||
/// false.
|
||||
virtual bool isX86HLT(const MCInst &Inst) const { return false; }
|
||||
|
||||
/// Return the width, in bytes, of the memory access performed by \p Inst, if
|
||||
/// this is a pop instruction. Return zero otherwise.
|
||||
virtual int getPopSize(const MCInst &Inst) const {
|
||||
|
||||
@ -2517,7 +2517,7 @@ BinaryContext::calculateEmittedSize(BinaryFunction &BF, bool FixBranches) {
|
||||
// Clean-up the effect of the code emission.
|
||||
for (const MCSymbol &Symbol : Assembler.symbols()) {
|
||||
MCSymbol *MutableSymbol = const_cast<MCSymbol *>(&Symbol);
|
||||
MutableSymbol->setUndefined();
|
||||
MutableSymbol->setFragment(nullptr);
|
||||
MutableSymbol->setIsRegistered(false);
|
||||
}
|
||||
|
||||
|
||||
@ -132,8 +132,10 @@ bool MCPlusBuilder::equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B,
|
||||
}
|
||||
|
||||
bool MCPlusBuilder::isTerminator(const MCInst &Inst) const {
|
||||
return Analysis->isTerminator(Inst) ||
|
||||
(opts::TerminalTrap && Info->get(Inst.getOpcode()).isTrap());
|
||||
return (opts::TerminalTrap && Info->get(Inst.getOpcode()).isTrap()) ||
|
||||
Analysis->isTerminator(Inst)
|
||||
? !isX86HLT(Inst)
|
||||
: false;
|
||||
}
|
||||
|
||||
void MCPlusBuilder::setTailCall(MCInst &Inst) const {
|
||||
|
||||
@ -662,7 +662,7 @@ Error CleanMCState::runOnFunctions(BinaryContext &BC) {
|
||||
if (S->isDefined()) {
|
||||
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Symbol \"" << S->getName()
|
||||
<< "\" is already defined\n");
|
||||
const_cast<MCSymbol *>(S)->setUndefined();
|
||||
const_cast<MCSymbol *>(S)->setFragment(nullptr);
|
||||
}
|
||||
if (S->isRegistered()) {
|
||||
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Symbol \"" << S->getName()
|
||||
|
||||
@ -223,6 +223,10 @@ public:
|
||||
return Inst.getOpcode() == X86::ENDBR32 || Inst.getOpcode() == X86::ENDBR64;
|
||||
}
|
||||
|
||||
bool isX86HLT(const MCInst &Inst) const override {
|
||||
return Inst.getOpcode() == X86::HLT;
|
||||
}
|
||||
|
||||
int getPopSize(const MCInst &Inst) const override {
|
||||
switch (Inst.getOpcode()) {
|
||||
case X86::POP16r:
|
||||
|
||||
17
bolt/test/X86/cfg_build_hlt.s
Normal file
17
bolt/test/X86/cfg_build_hlt.s
Normal file
@ -0,0 +1,17 @@
|
||||
## Check CFG for halt instruction
|
||||
|
||||
# RUN: %clang %cflags %s -static -o %t.exe -nostdlib
|
||||
# RUN: llvm-bolt %t.exe --print-cfg --print-only=main -o %t 2>&1 | FileCheck %s --check-prefix=CHECK-CFG
|
||||
# RUN: llvm-objdump -d %t --print-imm-hex | FileCheck %s --check-prefix=CHECK-BIN
|
||||
|
||||
# CHECK-CFG: BB Count : 1
|
||||
# CHECK-BIN: <main>:
|
||||
# CHECK-BIN-NEXT: f4 hlt
|
||||
# CHECK-BIN-NEXT: c3 retq
|
||||
|
||||
.global main
|
||||
.type main, %function
|
||||
main:
|
||||
hlt
|
||||
retq
|
||||
.size main, .-main
|
||||
@ -424,6 +424,10 @@ ClangTidyASTConsumerFactory::createASTConsumer(
|
||||
FinderOptions.CheckProfiling.emplace(Profiling->Records);
|
||||
}
|
||||
|
||||
// Avoid processing system headers, unless the user explicitly requests it
|
||||
if (!Context.getOptions().SystemHeaders.value_or(false))
|
||||
FinderOptions.IgnoreSystemHeaders = true;
|
||||
|
||||
std::unique_ptr<ast_matchers::MatchFinder> Finder(
|
||||
new ast_matchers::MatchFinder(std::move(FinderOptions)));
|
||||
|
||||
|
||||
@ -191,6 +191,9 @@ void PreferMemberInitializerCheck::check(
|
||||
if (!AssignmentToMember)
|
||||
continue;
|
||||
const FieldDecl *Field = AssignmentToMember->Field;
|
||||
// Skip if the field is inherited from a base class.
|
||||
if (Field->getParent() != Class)
|
||||
continue;
|
||||
const Expr *InitValue = AssignmentToMember->Init;
|
||||
updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields);
|
||||
if (!canAdvanceAssignment(AssignedFields[Field]))
|
||||
|
||||
@ -32,6 +32,7 @@ add_clang_library(clangTidyMiscModule STATIC
|
||||
NoRecursionCheck.cpp
|
||||
NonCopyableObjects.cpp
|
||||
NonPrivateMemberVariablesInClassesCheck.cpp
|
||||
OverrideWithDifferentVisibilityCheck.cpp
|
||||
RedundantExpressionCheck.cpp
|
||||
StaticAssertCheck.cpp
|
||||
ThrowByValueCatchByReferenceCheck.cpp
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#include "NoRecursionCheck.h"
|
||||
#include "NonCopyableObjects.h"
|
||||
#include "NonPrivateMemberVariablesInClassesCheck.h"
|
||||
#include "OverrideWithDifferentVisibilityCheck.h"
|
||||
#include "RedundantExpressionCheck.h"
|
||||
#include "StaticAssertCheck.h"
|
||||
#include "ThrowByValueCatchByReferenceCheck.h"
|
||||
@ -81,6 +82,8 @@ public:
|
||||
"misc-use-anonymous-namespace");
|
||||
CheckFactories.registerCheck<UseInternalLinkageCheck>(
|
||||
"misc-use-internal-linkage");
|
||||
CheckFactories.registerCheck<OverrideWithDifferentVisibilityCheck>(
|
||||
"misc-override-with-different-visibility");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,150 @@
|
||||
//===--- OverrideWithDifferentVisibilityCheck.cpp - clang-tidy ------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "OverrideWithDifferentVisibilityCheck.h"
|
||||
#include "../utils/Matchers.h"
|
||||
#include "../utils/OptionsUtils.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
using namespace clang;
|
||||
|
||||
namespace {
|
||||
|
||||
AST_MATCHER(NamedDecl, isOperatorDecl) {
|
||||
DeclarationName::NameKind const NK = Node.getDeclName().getNameKind();
|
||||
return NK != DeclarationName::Identifier &&
|
||||
NK != DeclarationName::CXXConstructorName &&
|
||||
NK != DeclarationName::CXXDestructorName;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace clang::tidy {
|
||||
|
||||
template <>
|
||||
struct OptionEnumMapping<
|
||||
misc::OverrideWithDifferentVisibilityCheck::ChangeKind> {
|
||||
static llvm::ArrayRef<std::pair<
|
||||
misc::OverrideWithDifferentVisibilityCheck::ChangeKind, StringRef>>
|
||||
getEnumMapping() {
|
||||
static constexpr std::pair<
|
||||
misc::OverrideWithDifferentVisibilityCheck::ChangeKind, StringRef>
|
||||
Mapping[] = {
|
||||
{misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Any,
|
||||
"any"},
|
||||
{misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Widening,
|
||||
"widening"},
|
||||
{misc::OverrideWithDifferentVisibilityCheck::ChangeKind::Narrowing,
|
||||
"narrowing"},
|
||||
};
|
||||
return {Mapping};
|
||||
}
|
||||
};
|
||||
|
||||
namespace misc {
|
||||
|
||||
OverrideWithDifferentVisibilityCheck::OverrideWithDifferentVisibilityCheck(
|
||||
StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context),
|
||||
DetectVisibilityChange(
|
||||
Options.get("DisallowedVisibilityChange", ChangeKind::Any)),
|
||||
CheckDestructors(Options.get("CheckDestructors", false)),
|
||||
CheckOperators(Options.get("CheckOperators", false)),
|
||||
IgnoredFunctions(utils::options::parseStringList(
|
||||
Options.get("IgnoredFunctions", ""))) {}
|
||||
|
||||
void OverrideWithDifferentVisibilityCheck::storeOptions(
|
||||
ClangTidyOptions::OptionMap &Opts) {
|
||||
Options.store(Opts, "DisallowedVisibilityChange", DetectVisibilityChange);
|
||||
Options.store(Opts, "CheckDestructors", CheckDestructors);
|
||||
Options.store(Opts, "CheckOperators", CheckOperators);
|
||||
Options.store(Opts, "IgnoredFunctions",
|
||||
utils::options::serializeStringList(IgnoredFunctions));
|
||||
}
|
||||
|
||||
void OverrideWithDifferentVisibilityCheck::registerMatchers(
|
||||
MatchFinder *Finder) {
|
||||
const auto IgnoredDecl =
|
||||
namedDecl(matchers::matchesAnyListedName(IgnoredFunctions));
|
||||
const auto FilterDestructors =
|
||||
CheckDestructors ? decl() : decl(unless(cxxDestructorDecl()));
|
||||
const auto FilterOperators =
|
||||
CheckOperators ? namedDecl() : namedDecl(unless(isOperatorDecl()));
|
||||
Finder->addMatcher(
|
||||
cxxMethodDecl(
|
||||
isVirtual(), FilterDestructors, FilterOperators,
|
||||
ofClass(
|
||||
cxxRecordDecl(unless(isExpansionInSystemHeader())).bind("class")),
|
||||
forEachOverridden(cxxMethodDecl(ofClass(cxxRecordDecl().bind("base")),
|
||||
unless(IgnoredDecl))
|
||||
.bind("base_func")))
|
||||
.bind("func"),
|
||||
this);
|
||||
}
|
||||
|
||||
void OverrideWithDifferentVisibilityCheck::check(
|
||||
const MatchFinder::MatchResult &Result) {
|
||||
const auto *const MatchedFunction =
|
||||
Result.Nodes.getNodeAs<FunctionDecl>("func");
|
||||
if (!MatchedFunction->isCanonicalDecl())
|
||||
return;
|
||||
|
||||
const auto *const ParentClass =
|
||||
Result.Nodes.getNodeAs<CXXRecordDecl>("class");
|
||||
const auto *const BaseClass = Result.Nodes.getNodeAs<CXXRecordDecl>("base");
|
||||
CXXBasePaths Paths;
|
||||
if (!ParentClass->isDerivedFrom(BaseClass, Paths))
|
||||
return;
|
||||
|
||||
const auto *const OverriddenFunction =
|
||||
Result.Nodes.getNodeAs<FunctionDecl>("base_func");
|
||||
AccessSpecifier const ActualAccess = MatchedFunction->getAccess();
|
||||
AccessSpecifier OverriddenAccess = OverriddenFunction->getAccess();
|
||||
|
||||
const CXXBaseSpecifier *InheritanceWithStrictVisibility = nullptr;
|
||||
for (const CXXBasePath &Path : Paths) {
|
||||
for (const CXXBasePathElement &Elem : Path) {
|
||||
if (Elem.Base->getAccessSpecifier() > OverriddenAccess) {
|
||||
OverriddenAccess = Elem.Base->getAccessSpecifier();
|
||||
InheritanceWithStrictVisibility = Elem.Base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ActualAccess != OverriddenAccess) {
|
||||
if (DetectVisibilityChange == ChangeKind::Widening &&
|
||||
ActualAccess > OverriddenAccess)
|
||||
return;
|
||||
if (DetectVisibilityChange == ChangeKind::Narrowing &&
|
||||
ActualAccess < OverriddenAccess)
|
||||
return;
|
||||
|
||||
if (InheritanceWithStrictVisibility) {
|
||||
diag(MatchedFunction->getLocation(),
|
||||
"visibility of function %0 is changed from %1 (through %1 "
|
||||
"inheritance of class %2) to %3")
|
||||
<< MatchedFunction << OverriddenAccess
|
||||
<< InheritanceWithStrictVisibility->getType() << ActualAccess;
|
||||
diag(InheritanceWithStrictVisibility->getBeginLoc(),
|
||||
"%0 is inherited as %1 here", DiagnosticIDs::Note)
|
||||
<< InheritanceWithStrictVisibility->getType() << OverriddenAccess;
|
||||
} else {
|
||||
diag(MatchedFunction->getLocation(),
|
||||
"visibility of function %0 is changed from %1 in class %2 to %3")
|
||||
<< MatchedFunction << OverriddenAccess << BaseClass << ActualAccess;
|
||||
}
|
||||
diag(OverriddenFunction->getLocation(), "function declared here as %0",
|
||||
DiagnosticIDs::Note)
|
||||
<< OverriddenFunction->getAccess();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace misc
|
||||
|
||||
} // namespace clang::tidy
|
||||
@ -0,0 +1,43 @@
|
||||
//===--- OverrideWithDifferentVisibilityCheck.h - clang-tidy --*- C++ -*---===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OVERRIDEWITHDIFFERENTVISIBILITYCHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OVERRIDEWITHDIFFERENTVISIBILITYCHECK_H
|
||||
|
||||
#include "../ClangTidyCheck.h"
|
||||
|
||||
namespace clang::tidy::misc {
|
||||
|
||||
/// Finds virtual function overrides with different visibility than the function
|
||||
/// in the base class.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/misc/override-with-different-visibility.html
|
||||
class OverrideWithDifferentVisibilityCheck : public ClangTidyCheck {
|
||||
public:
|
||||
enum class ChangeKind { Any, Widening, Narrowing };
|
||||
|
||||
OverrideWithDifferentVisibilityCheck(StringRef Name,
|
||||
ClangTidyContext *Context);
|
||||
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
|
||||
return LangOpts.CPlusPlus;
|
||||
}
|
||||
|
||||
private:
|
||||
ChangeKind DetectVisibilityChange;
|
||||
bool CheckDestructors;
|
||||
bool CheckOperators;
|
||||
std::vector<llvm::StringRef> IgnoredFunctions;
|
||||
};
|
||||
|
||||
} // namespace clang::tidy::misc
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OVERRIDEWITHDIFFERENTVISIBILITYCHECK_H
|
||||
@ -28,6 +28,7 @@ import glob
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
@ -42,13 +43,6 @@ try:
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
is_py2 = sys.version[0] == "2"
|
||||
|
||||
if is_py2:
|
||||
import Queue as queue
|
||||
else:
|
||||
import queue as queue
|
||||
|
||||
|
||||
def run_tidy(task_queue, lock, timeout, failed_files):
|
||||
watchdog = None
|
||||
|
||||
@ -104,6 +104,10 @@ Improvements to clang-query
|
||||
Improvements to clang-tidy
|
||||
--------------------------
|
||||
|
||||
- :program:`clang-tidy` no longer attemps to analyze code from system headers
|
||||
by default, greatly improving performance. This behavior is disabled if the
|
||||
`SystemHeaders` option is enabled.
|
||||
|
||||
- The :program:`run-clang-tidy.py` and :program:`clang-tidy-diff.py` scripts
|
||||
now run checks in parallel by default using all available hardware threads.
|
||||
Both scripts display the number of threads being used in their output.
|
||||
@ -130,6 +134,12 @@ New checks
|
||||
Checks for uses of MLIR's old/to be deprecated ``OpBuilder::create<T>`` form
|
||||
and suggests using ``T::create`` instead.
|
||||
|
||||
- New :doc:`misc-override-with-different-visibility
|
||||
<clang-tidy/checks/misc/override-with-different-visibility>` check.
|
||||
|
||||
Finds virtual function overrides with different visibility than the function
|
||||
in the base class.
|
||||
|
||||
New check aliases
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -163,6 +173,10 @@ Changes in existing checks
|
||||
an additional matcher that generalizes the copy-and-swap idiom pattern
|
||||
detection.
|
||||
|
||||
- Improved :doc:`cppcoreguidelines-prefer-member-initializer
|
||||
<clang-tidy/checks/cppcoreguidelines/prefer-member-initializer>` check to
|
||||
avoid false positives on inherited members in class templates.
|
||||
|
||||
- Improved :doc:`misc-header-include-cycle
|
||||
<clang-tidy/checks/misc/header-include-cycle>` check performance.
|
||||
|
||||
|
||||
@ -271,6 +271,7 @@ Clang-Tidy Checks
|
||||
:doc:`misc-no-recursion <misc/no-recursion>`,
|
||||
:doc:`misc-non-copyable-objects <misc/non-copyable-objects>`,
|
||||
:doc:`misc-non-private-member-variables-in-classes <misc/non-private-member-variables-in-classes>`,
|
||||
:doc:`misc-override-with-different-visibility <misc/override-with-different-visibility>`,
|
||||
:doc:`misc-redundant-expression <misc/redundant-expression>`, "Yes"
|
||||
:doc:`misc-static-assert <misc/static-assert>`, "Yes"
|
||||
:doc:`misc-throw-by-value-catch-by-reference <misc/throw-by-value-catch-by-reference>`,
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
.. title:: clang-tidy - misc-override-with-different-visibility
|
||||
|
||||
misc-override-with-different-visibility
|
||||
=======================================
|
||||
|
||||
Finds virtual function overrides with different visibility than the function
|
||||
in the base class. This includes for example if a virtual function declared as
|
||||
``private`` is overridden and declared as ``public`` in a subclass. The detected
|
||||
change is the modification of visibility resulting from keywords ``public``,
|
||||
``protected``, ``private`` at overridden virtual functions. The check applies to
|
||||
any normal virtual function and optionally to destructors or operators. Use of
|
||||
the ``using`` keyword is not considered as visibility change by this check.
|
||||
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
class A {
|
||||
public:
|
||||
virtual void f_pub();
|
||||
private:
|
||||
virtual void f_priv();
|
||||
};
|
||||
|
||||
class B: public A {
|
||||
public:
|
||||
void f_priv(); // warning: changed visibility from private to public
|
||||
private:
|
||||
void f_pub(); // warning: changed visibility from public to private
|
||||
};
|
||||
|
||||
class C: private A {
|
||||
// no warning: f_pub becomes private in this case but this is from the
|
||||
// private inheritance
|
||||
};
|
||||
|
||||
class D: private A {
|
||||
public:
|
||||
void f_pub(); // warning: changed visibility from private to public
|
||||
// 'f_pub' would have private access but is forced to be
|
||||
// public
|
||||
};
|
||||
|
||||
If the visibility is changed in this way, it can indicate bad design or
|
||||
programming error.
|
||||
|
||||
If a virtual function is private in a subclass but public in the base class, it
|
||||
can still be accessed from a pointer to the subclass if the pointer is converted
|
||||
to the base type. Probably private inheritance can be used instead.
|
||||
|
||||
A protected virtual function that is made public in a subclass may have valid
|
||||
use cases but similar (not exactly same) effect can be achieved with the
|
||||
``using`` keyword.
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
.. option:: DisallowedVisibilityChange
|
||||
|
||||
Controls what kind of change to the visibility will be detected by the check.
|
||||
Possible values are `any`, `widening`, `narrowing`. For example the
|
||||
`widening` option will produce warning only if the visibility is changed
|
||||
from more restrictive (``private``) to less restrictive (``public``).
|
||||
Default value is `any`.
|
||||
|
||||
.. option:: CheckDestructors
|
||||
|
||||
If `true`, the check does apply to destructors too. Otherwise destructors
|
||||
are ignored by the check.
|
||||
Default value is `false`.
|
||||
|
||||
.. option:: CheckOperators
|
||||
|
||||
If `true`, the check does apply to overloaded C++ operators (as virtual
|
||||
member functions) too. This includes other special member functions (like
|
||||
conversions) too. This option is probably useful only in rare cases because
|
||||
operators and conversions are not often virtual functions.
|
||||
Default value is `false`.
|
||||
|
||||
.. option:: IgnoredFunctions
|
||||
|
||||
This option can be used to ignore the check at specific functions.
|
||||
To configure this option, a semicolon-separated list of function names
|
||||
should be provided. The list can contain regular expressions, in this way it
|
||||
is possible to select all functions of a specific class (like `MyClass::.*`)
|
||||
or a specific function of any class (like `my_function` or
|
||||
`::.*::my_function`). The function names are matched at the base class.
|
||||
Default value is empty string.
|
||||
@ -111,6 +111,13 @@ Diagnostics which have a corresponding warning option, are named
|
||||
``-Wliteral-conversion`` will be reported with check name
|
||||
``clang-diagnostic-literal-conversion``.
|
||||
|
||||
Clang compiler errors (such as syntax errors, semantic errors, or other failures
|
||||
that prevent Clang from compiling the code) are reported with the check name
|
||||
``clang-diagnostic-error``. These represent fundamental compilation failures that
|
||||
must be fixed before :program:`clang-tidy` can perform its analysis. Unlike other
|
||||
diagnostics, ``clang-diagnostic-error`` cannot be disabled, as :program:`clang-tidy`
|
||||
requires valid code to function.
|
||||
|
||||
The ``-fix`` flag instructs :program:`clang-tidy` to fix found errors if
|
||||
supported by corresponding checks.
|
||||
|
||||
|
||||
@ -650,3 +650,16 @@ struct InitFromBindingDecl {
|
||||
}
|
||||
};
|
||||
} // namespace GH82970
|
||||
|
||||
struct A {
|
||||
int m;
|
||||
};
|
||||
|
||||
struct B : A {
|
||||
B() { m = 0; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct C : A {
|
||||
C() { m = 0; }
|
||||
};
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
#pragma clang system_header
|
||||
|
||||
namespace sys {
|
||||
|
||||
struct Base {
|
||||
virtual void publicF();
|
||||
};
|
||||
|
||||
struct Derived: public Base {
|
||||
private:
|
||||
void publicF() override;
|
||||
};
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
// RUN: %check_clang_tidy %s misc-override-with-different-visibility %t -- \
|
||||
// RUN: -config="{CheckOptions: {misc-override-with-different-visibility.IgnoredFunctions: 'IgnoreAlways::.*;::a::IgnoreSelected::.*;IgnoreFunctions::f1;ignored_f'}}"
|
||||
|
||||
class IgnoreAlways {
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
class IgnoreSelected {
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
namespace a {
|
||||
class IgnoreAlways {
|
||||
virtual void f();
|
||||
};
|
||||
class IgnoreSelected {
|
||||
virtual void f();
|
||||
};
|
||||
}
|
||||
|
||||
namespace ignore_always {
|
||||
class Test1: public IgnoreAlways {
|
||||
public:
|
||||
void f();
|
||||
void ignored_f(int);
|
||||
};
|
||||
class Test2: public a::IgnoreAlways {
|
||||
public:
|
||||
void f();
|
||||
};
|
||||
}
|
||||
|
||||
namespace ignore_selected {
|
||||
class Test1: public IgnoreSelected {
|
||||
public:
|
||||
void f();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'f'
|
||||
// CHECK-MESSAGES: :9:16: note: function declared here
|
||||
void ignored_f(int);
|
||||
};
|
||||
class Test2: public a::IgnoreSelected {
|
||||
public:
|
||||
void f();
|
||||
};
|
||||
}
|
||||
|
||||
class IgnoreFunctions {
|
||||
virtual void f1();
|
||||
virtual void f2();
|
||||
virtual void ignored_f();
|
||||
};
|
||||
|
||||
class IgnoreFunctionsTest: public IgnoreFunctions {
|
||||
public:
|
||||
void f1();
|
||||
void f2();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'f2'
|
||||
// CHECK-MESSAGES: :[[@LINE-9]]:16: note: function declared here
|
||||
void ignored_f();
|
||||
};
|
||||
@ -0,0 +1,75 @@
|
||||
// RUN: %check_clang_tidy -check-suffixes=DTORS,WIDENING,NARROWING %s misc-override-with-different-visibility %t -- \
|
||||
// RUN: -config="{CheckOptions: {misc-override-with-different-visibility.CheckDestructors: true}}"
|
||||
|
||||
// RUN: %check_clang_tidy -check-suffixes=OPS,WIDENING,NARROWING %s misc-override-with-different-visibility %t -- \
|
||||
// RUN: -config="{CheckOptions: {misc-override-with-different-visibility.CheckOperators: true}}"
|
||||
|
||||
// RUN: %check_clang_tidy -check-suffixes=WIDENING %s misc-override-with-different-visibility %t -- \
|
||||
// RUN: -config="{CheckOptions: {misc-override-with-different-visibility.DisallowedVisibilityChange: 'widening'}}"
|
||||
|
||||
// RUN: %check_clang_tidy -check-suffixes=NARROWING %s misc-override-with-different-visibility %t -- \
|
||||
// RUN: -config="{CheckOptions: {misc-override-with-different-visibility.DisallowedVisibilityChange: 'narrowing'}}"
|
||||
|
||||
namespace test_change {
|
||||
|
||||
class A {
|
||||
protected:
|
||||
virtual void f1();
|
||||
virtual void f2();
|
||||
};
|
||||
|
||||
class B: public A {
|
||||
public:
|
||||
void f1();
|
||||
// CHECK-MESSAGES-WIDENING: :[[@LINE-1]]:8: warning: visibility of function 'f1'
|
||||
// CHECK-MESSAGES-WIDENING: :[[@LINE-8]]:16: note: function declared here
|
||||
private:
|
||||
void f2();
|
||||
// CHECK-MESSAGES-NARROWING: :[[@LINE-1]]:8: warning: visibility of function 'f2'
|
||||
// CHECK-MESSAGES-NARROWING: :[[@LINE-11]]:16: note: function declared here
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test_destructor {
|
||||
|
||||
class A {
|
||||
public:
|
||||
virtual ~A();
|
||||
};
|
||||
|
||||
class B: public A {
|
||||
protected:
|
||||
~B();
|
||||
// CHECK-MESSAGES-DTORS: :[[@LINE-1]]:3: warning: visibility of function '~B'
|
||||
// CHECK-MESSAGES-DTORS: :[[@LINE-7]]:11: note: function declared here
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test_operator {
|
||||
|
||||
class A {
|
||||
virtual A& operator=(const A&);
|
||||
virtual A& operator++();
|
||||
virtual int operator()(int);
|
||||
virtual operator double() const;
|
||||
};
|
||||
|
||||
class B: public A {
|
||||
protected:
|
||||
A& operator=(const A&);
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-1]]:6: warning: visibility of function 'operator='
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-10]]:14: note: function declared here
|
||||
A& operator++();
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-1]]:6: warning: visibility of function 'operator++'
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-12]]:14: note: function declared here
|
||||
int operator()(int);
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-1]]:7: warning: visibility of function 'operator()'
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-14]]:15: note: function declared here
|
||||
operator double() const;
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-1]]:3: warning: visibility of function 'operator double'
|
||||
// CHECK-MESSAGES-OPS: :[[@LINE-16]]:11: note: function declared here
|
||||
};
|
||||
|
||||
}
|
||||
@ -0,0 +1,289 @@
|
||||
// RUN: %check_clang_tidy %s misc-override-with-different-visibility %t -- -config="{CheckOptions: {misc-override-with-different-visibility.CheckDestructors: true,misc-override-with-different-visibility.CheckOperators: true}}" -- -I %S/Inputs/override-with-different-visibility
|
||||
#include <test-system-header.h>
|
||||
class A {
|
||||
public:
|
||||
virtual void pub_foo1() {}
|
||||
virtual void pub_foo2() {}
|
||||
virtual void pub_foo3() {}
|
||||
protected:
|
||||
virtual void prot_foo1();
|
||||
virtual void prot_foo2();
|
||||
virtual void prot_foo3();
|
||||
private:
|
||||
virtual void priv_foo1() {}
|
||||
virtual void priv_foo2() {}
|
||||
virtual void priv_foo3() {}
|
||||
};
|
||||
|
||||
void A::prot_foo1() {}
|
||||
void A::prot_foo2() {}
|
||||
void A::prot_foo3() {}
|
||||
|
||||
namespace test1 {
|
||||
|
||||
class B: public A {
|
||||
public:
|
||||
void pub_foo1() override {}
|
||||
void prot_foo1() override {}
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from protected in class 'A' to public [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :9:16: note: function declared here as protected
|
||||
void priv_foo1() override {}
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from private in class 'A' to public [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :13:16: note: function declared here as private
|
||||
protected:
|
||||
void pub_foo2() override {}
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo2' is changed from public in class 'A' to protected [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :6:16: note: function declared here as public
|
||||
void prot_foo2() override {}
|
||||
void priv_foo2() override {}
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to protected [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :14:16: note: function declared here as private
|
||||
private:
|
||||
void pub_foo3() override {}
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo3' is changed from public in class 'A' to private [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :7:16: note: function declared here as public
|
||||
void prot_foo3() override {}
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo3' is changed from protected in class 'A' to private [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :11:16: note: function declared here as protected
|
||||
void priv_foo3() override {}
|
||||
};
|
||||
|
||||
class C: public B {
|
||||
public:
|
||||
void pub_foo1() override;
|
||||
protected:
|
||||
void prot_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from public in class 'B' to protected [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :27:8: note: function declared here as public
|
||||
private:
|
||||
void priv_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from public in class 'B' to private [misc-override-with-different-visibility]
|
||||
// CHECK-MESSAGES: :30:8: note: function declared here as public
|
||||
};
|
||||
|
||||
void C::prot_foo1() {}
|
||||
void C::priv_foo1() {}
|
||||
|
||||
}
|
||||
|
||||
namespace test2 {
|
||||
|
||||
class B: public A {
|
||||
public:
|
||||
void pub_foo1() override;
|
||||
protected:
|
||||
void prot_foo1() override;
|
||||
private:
|
||||
void priv_foo1() override;
|
||||
};
|
||||
|
||||
class C: public B {
|
||||
public:
|
||||
void pub_foo1() override;
|
||||
void prot_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from protected in class 'B' to public
|
||||
// CHECK-MESSAGES: :75:8: note: function declared here as protected
|
||||
void priv_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo1' is changed from private in class 'B' to public
|
||||
// CHECK-MESSAGES: :77:8: note: function declared here as private
|
||||
|
||||
void pub_foo2() override;
|
||||
void prot_foo2() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo2' is changed from protected in class 'A' to public
|
||||
// CHECK-MESSAGES: :10:16: note: function declared here as protected
|
||||
void priv_foo2() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to public
|
||||
// CHECK-MESSAGES: :14:16: note: function declared here as private
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test3 {
|
||||
|
||||
class B: private A {
|
||||
public:
|
||||
void pub_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public
|
||||
// CHECK-MESSAGES: :103:10: note: 'A' is inherited as private here
|
||||
// CHECK-MESSAGES: :5:16: note: function declared here as public
|
||||
protected:
|
||||
void prot_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo1' is changed from private (through private inheritance of class 'A') to protected
|
||||
// CHECK-MESSAGES: :103:10: note: 'A' is inherited as private here
|
||||
// CHECK-MESSAGES: :9:16: note: function declared here as protected
|
||||
private:
|
||||
void priv_foo1() override;
|
||||
|
||||
public:
|
||||
void prot_foo2() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'prot_foo2' is changed from private (through private inheritance of class 'A') to public
|
||||
// CHECK-MESSAGES: :103:10: note: 'A' is inherited as private here
|
||||
// CHECK-MESSAGES: :10:16: note: function declared here as protected
|
||||
void priv_foo2() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'priv_foo2' is changed from private in class 'A' to public
|
||||
// CHECK-MESSAGES: :14:16: note: function declared here as private
|
||||
|
||||
private:
|
||||
void pub_foo3() override;
|
||||
void prot_foo3() override;
|
||||
};
|
||||
|
||||
class C: private A {
|
||||
};
|
||||
|
||||
class D: public C {
|
||||
public:
|
||||
void pub_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public
|
||||
// CHECK-MESSAGES: :131:10: note: 'A' is inherited as private here
|
||||
// CHECK-MESSAGES: :5:16: note: function declared here as public
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace test4 {
|
||||
|
||||
struct Base1 {
|
||||
public:
|
||||
virtual void foo1();
|
||||
private:
|
||||
virtual void foo2();
|
||||
};
|
||||
|
||||
struct Base2 {
|
||||
public:
|
||||
virtual void foo2();
|
||||
private:
|
||||
virtual void foo1();
|
||||
};
|
||||
|
||||
struct A : public Base1, public Base2 {
|
||||
protected:
|
||||
void foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'foo1' is changed from private in class 'Base2' to protected
|
||||
// CHECK-MESSAGES: :158:16: note: function declared here as private
|
||||
// CHECK-MESSAGES: :[[@LINE-3]]:8: warning: visibility of function 'foo1' is changed from public in class 'Base1' to protected
|
||||
// CHECK-MESSAGES: :149:16: note: function declared here as public
|
||||
private:
|
||||
void foo2() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'foo2' is changed from public in class 'Base2' to private
|
||||
// CHECK-MESSAGES: :156:16: note: function declared here as public
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test5 {
|
||||
|
||||
struct B1: virtual public A {};
|
||||
struct B2: virtual private A {};
|
||||
struct B: public B1, public B2 {
|
||||
public:
|
||||
void pub_foo1() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'pub_foo1' is changed from private (through private inheritance of class 'A') to public
|
||||
// CHECK-MESSAGES: :179:12: note: 'A' is inherited as private here
|
||||
// CHECK-MESSAGES: :5:16: note: function declared here as public
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test_using {
|
||||
|
||||
class A {
|
||||
private:
|
||||
A(int);
|
||||
protected:
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
class B: public A {
|
||||
public:
|
||||
using A::A;
|
||||
using A::f;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test_template {
|
||||
|
||||
template <typename T>
|
||||
class A {
|
||||
protected:
|
||||
virtual T foo();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class B: public A<T> {
|
||||
private:
|
||||
T foo() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: visibility of function 'foo' is changed from protected in class 'A<int>' to private
|
||||
// CHECK-MESSAGES: :[[@LINE-8]]:13: note: function declared here as protected
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class C: private A<T> {
|
||||
public:
|
||||
T foo() override;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: visibility of function 'foo' is changed from private (through private inheritance of class 'A<int>') to public
|
||||
// CHECK-MESSAGES: :[[@LINE-4]]:10: note: 'A<int>' is inherited as private here
|
||||
// CHECK-MESSAGES: :[[@LINE-17]]:13: note: function declared here as protected
|
||||
};
|
||||
|
||||
B<int> fB() {
|
||||
return B<int>{};
|
||||
}
|
||||
|
||||
C<int> fC() {
|
||||
return C<int>{};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace test_system_header {
|
||||
|
||||
struct SysDerived: public sys::Base {
|
||||
private:
|
||||
void publicF();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: visibility of function 'publicF' is changed from public in class 'Base' to private
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test_destructor {
|
||||
|
||||
class A {
|
||||
public:
|
||||
virtual ~A();
|
||||
};
|
||||
|
||||
class B: public A {
|
||||
protected:
|
||||
~B();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: visibility of function '~B'
|
||||
// CHECK-MESSAGES: :[[@LINE-7]]:11: note: function declared here
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace test_operator {
|
||||
|
||||
class A {
|
||||
virtual int operator()(int);
|
||||
virtual A& operator++();
|
||||
virtual operator double() const;
|
||||
};
|
||||
|
||||
class B: public A {
|
||||
protected:
|
||||
int operator()(int);
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: visibility of function 'operator()'
|
||||
// CHECK-MESSAGES: :[[@LINE-9]]:15: note: function declared here
|
||||
A& operator++();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: visibility of function 'operator++'
|
||||
// CHECK-MESSAGES: :[[@LINE-11]]:14: note: function declared here
|
||||
operator double() const;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: visibility of function 'operator double'
|
||||
// CHECK-MESSAGES: :[[@LINE-13]]:11: note: function declared here
|
||||
};
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
// RUN: %check_clang_tidy -std=c++20 %s modernize-type-traits %t
|
||||
|
||||
namespace std {
|
||||
template <class> struct tuple_size {
|
||||
static const int value = 1;
|
||||
};
|
||||
template <int, class> struct tuple_element {
|
||||
using type = int;
|
||||
};
|
||||
}
|
||||
|
||||
struct A {};
|
||||
template <int> int get(const A&);
|
||||
|
||||
auto [a] = A();
|
||||
@ -66,19 +66,14 @@ class A { A(int); };
|
||||
// CHECK4-NOT: warning:
|
||||
// CHECK4-QUIET-NOT: warning:
|
||||
|
||||
// CHECK: Suppressed 3 warnings (3 in non-user code)
|
||||
// CHECK: Use -header-filter=.* to display errors from all non-system headers.
|
||||
// CHECK-QUIET-NOT: Suppressed
|
||||
// CHECK2: Suppressed 1 warnings (1 in non-user code)
|
||||
// CHECK2: Use -header-filter=.* {{.*}}
|
||||
// CHECK2-QUIET-NOT: Suppressed
|
||||
// CHECK3: Suppressed 2 warnings (2 in non-user code)
|
||||
// CHECK3: Use -header-filter=.* {{.*}}
|
||||
// CHECK3-QUIET-NOT: Suppressed
|
||||
// CHECK4-NOT: Suppressed {{.*}} warnings
|
||||
// CHECK4-NOT: Use -header-filter=.* {{.*}}
|
||||
// CHECK4-QUIET-NOT: Suppressed
|
||||
// CHECK6: Suppressed 2 warnings (2 in non-user code)
|
||||
// CHECK6: Use -header-filter=.* {{.*}}
|
||||
|
||||
int x = 123;
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
// RUN: clang-tidy -help | FileCheck -check-prefix=CHECK-OPT-PRESENT %s
|
||||
|
||||
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers=true %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-SYSTEM-HEADERS %s
|
||||
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers=false %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS %s
|
||||
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers=false %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS --allow-empty %s
|
||||
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -config='SystemHeaders: true' %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-SYSTEM-HEADERS %s
|
||||
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -config='SystemHeaders: false' %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS %s
|
||||
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -config='SystemHeaders: false' %s -- -isystem %S/Inputs/system-headers 2>&1 | FileCheck -check-prefix=CHECK-NO-SYSTEM-HEADERS --allow-empty %s
|
||||
|
||||
#include <system_header.h>
|
||||
// CHECK-SYSTEM-HEADERS: system_header.h:1:13: warning: single-argument constructors must be marked explicit
|
||||
|
||||
@ -126,6 +126,9 @@ clang-format is turned off or back on.
|
||||
// clang-format on
|
||||
void formatted_code_again;
|
||||
|
||||
In addition, the ``OneLineFormatOffRegex`` option gives you a concise way to
|
||||
disable formatting for all of the lines that match the regular expression.
|
||||
|
||||
|
||||
Configuring Style in Code
|
||||
=========================
|
||||
@ -6483,13 +6486,51 @@ the configuration (without a prefix: ``Auto``).
|
||||
.. _SpaceInEmptyBlock:
|
||||
|
||||
**SpaceInEmptyBlock** (``Boolean``) :versionbadge:`clang-format 10` :ref:`¶ <SpaceInEmptyBlock>`
|
||||
If ``true``, spaces will be inserted into ``{}``.
|
||||
This option is **deprecated**. See ``Block`` of ``SpaceInEmptyBraces``.
|
||||
|
||||
.. _SpaceInEmptyBraces:
|
||||
|
||||
**SpaceInEmptyBraces** (``SpaceInEmptyBracesStyle``) :versionbadge:`clang-format 22` :ref:`¶ <SpaceInEmptyBraces>`
|
||||
Specifies when to insert a space in empty braces.
|
||||
|
||||
.. note::
|
||||
|
||||
This option doesn't apply to initializer braces if
|
||||
``Cpp11BracedListStyle`` is set to ``true``.
|
||||
|
||||
Possible values:
|
||||
|
||||
* ``SIEB_Always`` (in configuration: ``Always``)
|
||||
Always insert a space in empty braces.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
void f() { }
|
||||
class Unit { };
|
||||
auto a = [] { };
|
||||
int x{ };
|
||||
|
||||
* ``SIEB_Block`` (in configuration: ``Block``)
|
||||
Only insert a space in empty blocks.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
void f() { }
|
||||
class Unit { };
|
||||
auto a = [] { };
|
||||
int x{};
|
||||
|
||||
* ``SIEB_Never`` (in configuration: ``Never``)
|
||||
Never insert a space in empty braces.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
void f() {}
|
||||
class Unit {};
|
||||
auto a = [] {};
|
||||
int x{};
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
true: false:
|
||||
void f() { } vs. void f() {}
|
||||
while (true) { } while (true) {}
|
||||
|
||||
.. _SpaceInEmptyParentheses:
|
||||
|
||||
|
||||
@ -193,6 +193,8 @@ Bug Fixes in This Version
|
||||
targets that treat ``_Float16``/``__fp16`` as native scalar types. Previously
|
||||
the warning was silently lost because the operands differed only by an implicit
|
||||
cast chain. (#GH149967).
|
||||
- Fixed a crash with incompatible pointer to integer conversions in designated
|
||||
initializers involving string literals. (#GH154046)
|
||||
|
||||
Bug Fixes to Compiler Builtins
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -230,6 +232,7 @@ Bug Fixes to AST Handling
|
||||
- Fix incorrect name qualifiers applied to alias CTAD. (#GH136624)
|
||||
- Fixed ElaboratedTypes appearing within NestedNameSpecifier, which was not a
|
||||
legal representation. This is fixed because ElaboratedTypes don't exist anymore. (#GH43179) (#GH68670) (#GH92757)
|
||||
- Fix unrecognized html tag causing undesirable comment lexing (#GH152944)
|
||||
- Fix comment lexing of special command names (#GH152943)
|
||||
|
||||
Miscellaneous Bug Fixes
|
||||
@ -308,8 +311,12 @@ AST Matchers
|
||||
- Ensure ``hasBitWidth`` doesn't crash on bit widths that are dependent on template
|
||||
parameters.
|
||||
|
||||
- Add a boolean member ``IgnoreSystemHeaders`` to ``MatchFinderOptions``. This
|
||||
allows it to ignore nodes in system headers when traversing the AST.
|
||||
|
||||
clang-format
|
||||
------------
|
||||
- Add ``SpaceInEmptyBraces`` option and set it to ``Always`` for WebKit style.
|
||||
|
||||
libclang
|
||||
--------
|
||||
|
||||
@ -66,17 +66,17 @@ supported. Uninstrumented code simply won't be accounted for in reports.
|
||||
|
||||
To compile code with Modified Condition/Decision Coverage (MC/DC) enabled,
|
||||
pass ``-fcoverage-mcdc`` in addition to the clang options specified above.
|
||||
MC/DC is an advanced form of code coverage most applicable in the embedded
|
||||
MC/DC is an advanced form of code coverage most applicable to the embedded
|
||||
space.
|
||||
|
||||
Running the instrumented program
|
||||
================================
|
||||
|
||||
The next step is to run the instrumented program. When the program exits it
|
||||
The next step is to run the instrumented program. When the program exits, it
|
||||
will write a **raw profile** to the path specified by the ``LLVM_PROFILE_FILE``
|
||||
environment variable. If that variable does not exist, the profile is written
|
||||
to ``default.profraw`` in the current directory of the program. If
|
||||
``LLVM_PROFILE_FILE`` contains a path to a non-existent directory, the missing
|
||||
``LLVM_PROFILE_FILE`` specifies a path to a non-existent directory, the missing
|
||||
directory structure will be created. Additionally, the following special
|
||||
**pattern strings** are rewritten:
|
||||
|
||||
@ -97,7 +97,7 @@ directory structure will be created. Additionally, the following special
|
||||
* "%b" expands out to the binary ID (build ID). It can be used with "%Nm" to
|
||||
avoid binary signature collisions. To use it, the program should be compiled
|
||||
with the build ID linker option (``--build-id`` for GNU ld or LLD,
|
||||
``/build-id`` for lld-link on Windows). Linux, Windows and AIX are supported.
|
||||
``/build-id`` for lld-link on Windows). Linux, Windows, and AIX are supported.
|
||||
|
||||
* "%c" expands out to nothing, but enables a mode in which profile counter
|
||||
updates are continuously synced to a file. This means that if the
|
||||
@ -128,7 +128,7 @@ and set bias to the offset between the original and the new counter location,
|
||||
at which point every subsequent counter access will be to the new location,
|
||||
which allows updating profile directly akin to the continuous mode.
|
||||
|
||||
The advantage of this approach is that doesn't require any special OS support.
|
||||
The advantage of this approach is that it doesn't require any special OS support.
|
||||
The disadvantage is the extra overhead due to additional instructions required
|
||||
for each counter access (overhead both in terms of binary size and performance)
|
||||
plus duplication of counters (i.e. one copy in the binary itself and another
|
||||
@ -137,7 +137,7 @@ other platforms by passing the ``-runtime-counter-relocation`` option to the
|
||||
backend during compilation.
|
||||
|
||||
For a program such as the `Lit <https://llvm.org/docs/CommandGuide/lit.html>`_
|
||||
testing tool which invokes other programs, it may be necessary to set
|
||||
testing tool, which invokes other programs, it may be necessary to set
|
||||
``LLVM_PROFILE_FILE`` for each invocation. The pattern strings "%p" or "%Nm"
|
||||
may help to avoid corruption due to concurrency. Note that "%p" is also a Lit
|
||||
token and needs to be escaped as "%%p".
|
||||
@ -149,7 +149,7 @@ token and needs to be escaped as "%%p".
|
||||
Creating coverage reports
|
||||
=========================
|
||||
|
||||
Raw profiles have to be **indexed** before they can be used to generate
|
||||
Raw profiles must be **indexed** before they can be used to generate
|
||||
coverage reports. This is done using the "merge" tool in ``llvm-profdata``
|
||||
(which can combine multiple raw profiles and index them at the same time):
|
||||
|
||||
@ -240,13 +240,13 @@ line-oriented report, try:
|
||||
TOTAL 13 0 100.00% 3 0 100.00% 13 0 100.00% 12 2 83.33%
|
||||
|
||||
The ``llvm-cov`` tool supports specifying a custom demangler, writing out
|
||||
reports in a directory structure, and generating html reports. For the full
|
||||
reports in a directory structure, and generating HTML reports. For the full
|
||||
list of options, please refer to the `command guide
|
||||
<https://llvm.org/docs/CommandGuide/llvm-cov.html>`_.
|
||||
|
||||
A few final notes:
|
||||
|
||||
* The ``-sparse`` flag is optional but can result in dramatically smaller
|
||||
* The ``-sparse`` flag is optional but can produce dramatically smaller
|
||||
indexed profiles. This option should not be used if the indexed profile will
|
||||
be reused for PGO.
|
||||
|
||||
@ -255,7 +255,7 @@ A few final notes:
|
||||
information directly into an existing raw profile on disk. The details are
|
||||
out of scope.
|
||||
|
||||
* The ``llvm-profdata`` tool can be used to merge together multiple raw or
|
||||
* The ``llvm-profdata`` tool can be used to merge multiple raw or
|
||||
indexed profiles. To combine profiling data from multiple runs of a program,
|
||||
try e.g:
|
||||
|
||||
@ -299,7 +299,7 @@ There are six statistics tracked in a coverage summary:
|
||||
source code that may each evaluate to either "true" or "false". These
|
||||
conditions may comprise larger boolean expressions linked by boolean logical
|
||||
operators. For example, "x = (y == 2) || (z < 10)" is a boolean expression
|
||||
that is comprised of two individual conditions, each of which evaluates to
|
||||
comprised of two individual conditions, each of which evaluates to
|
||||
either true or false, producing four total branch outcomes.
|
||||
|
||||
* Modified Condition/Decision Coverage (MC/DC) is the percentage of individual
|
||||
@ -316,7 +316,7 @@ There are six statistics tracked in a coverage summary:
|
||||
``-show-mcdc-summary`` option as long as code was also compiled using the
|
||||
clang option ``-fcoverage-mcdc``.
|
||||
|
||||
* Boolean expressions that are only comprised of one condition (and therefore
|
||||
* Boolean expressions comprised of only one condition (and therefore
|
||||
have no logical operators) are not included in MC/DC analysis and are
|
||||
trivially deducible using branch coverage.
|
||||
|
||||
@ -366,7 +366,7 @@ By default the compiler runtime uses a static initializer to determine the
|
||||
profile output path and to register a writer function. To collect profiles
|
||||
without using static initializers, do this manually:
|
||||
|
||||
* Export a ``int __llvm_profile_runtime`` symbol from each instrumented shared
|
||||
* Export an ``int __llvm_profile_runtime`` symbol from each instrumented shared
|
||||
library and executable. When the linker finds a definition of this symbol, it
|
||||
knows to skip loading the object which contains the profiling runtime's
|
||||
static initializer.
|
||||
@ -380,7 +380,7 @@ without using static initializers, do this manually:
|
||||
to ``__llvm_profile_write_file``.
|
||||
|
||||
* Forward-declare ``int __llvm_profile_write_file(void)`` and call it to write
|
||||
out a profile. This function returns 0 when it succeeds, and a non-zero value
|
||||
out a profile. This function returns 0 on success, and a non-zero value
|
||||
otherwise. Calling this function multiple times appends profile data to an
|
||||
existing on-disk raw profile.
|
||||
|
||||
@ -418,7 +418,7 @@ Collecting coverage reports for the llvm project
|
||||
================================================
|
||||
|
||||
To prepare a coverage report for llvm (and any of its sub-projects), add
|
||||
``-DLLVM_BUILD_INSTRUMENTED_COVERAGE=On`` to the cmake configuration. Raw
|
||||
``-DLLVM_BUILD_INSTRUMENTED_COVERAGE=On`` to the CMake configuration. Raw
|
||||
profiles will be written to ``$BUILD_DIR/profiles/``. To prepare an html
|
||||
report, run ``llvm/utils/prepare-code-coverage-artifact.py``.
|
||||
|
||||
@ -429,7 +429,7 @@ To specify an alternate directory for raw profiles, use
|
||||
Drawbacks and limitations
|
||||
=========================
|
||||
|
||||
* Prior to version 2.26, the GNU binutils BFD linker is not able link programs
|
||||
* Prior to version 2.26, the GNU binutils BFD linker cannot link programs
|
||||
compiled with ``-fcoverage-mapping`` in its ``--gc-sections`` mode. Possible
|
||||
workarounds include disabling ``--gc-sections``, upgrading to a newer version
|
||||
of BFD, or using the Gold linker.
|
||||
|
||||
@ -51,6 +51,11 @@ def Col : Tag<"col"> { let EndTagForbidden = 1; }
|
||||
def Tr : Tag<"tr"> { let EndTagOptional = 1; }
|
||||
def Th : Tag<"th"> { let EndTagOptional = 1; }
|
||||
def Td : Tag<"td"> { let EndTagOptional = 1; }
|
||||
def Summary : Tag<"summary">;
|
||||
def Details : Tag<"details">;
|
||||
def Mark : Tag<"mark">;
|
||||
def Figure : Tag<"figure">;
|
||||
def FigCaption : Tag<"figcaption">;
|
||||
|
||||
// Define a list of attributes that are not safe to pass through to HTML
|
||||
// output if the input is untrusted.
|
||||
|
||||
@ -135,10 +135,15 @@ public:
|
||||
llvm::StringMap<llvm::TimeRecord> &Records;
|
||||
};
|
||||
|
||||
MatchFinderOptions() {}
|
||||
|
||||
/// Enables per-check timers.
|
||||
///
|
||||
/// It prints a report after match.
|
||||
std::optional<Profiling> CheckProfiling;
|
||||
|
||||
/// Avoids matching declarations in system headers.
|
||||
bool IgnoreSystemHeaders{false};
|
||||
};
|
||||
|
||||
MatchFinder(MatchFinderOptions Options = MatchFinderOptions());
|
||||
|
||||
@ -19,14 +19,35 @@
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "llvm/ADT/DenseMapInfo.h"
|
||||
#include "llvm/ADT/ImmutableMap.h"
|
||||
#include "llvm/ADT/ImmutableSet.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include <memory>
|
||||
|
||||
namespace clang::lifetimes {
|
||||
|
||||
/// Enum to track the confidence level of a potential error.
|
||||
enum class Confidence {
|
||||
None,
|
||||
Maybe, // Reported as a potential error (-Wlifetime-safety-strict)
|
||||
Definite // Reported as a definite error (-Wlifetime-safety-permissive)
|
||||
};
|
||||
|
||||
class LifetimeSafetyReporter {
|
||||
public:
|
||||
LifetimeSafetyReporter() = default;
|
||||
virtual ~LifetimeSafetyReporter() = default;
|
||||
|
||||
virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
|
||||
SourceLocation FreeLoc,
|
||||
Confidence Confidence) {}
|
||||
};
|
||||
|
||||
/// The main entry point for the analysis.
|
||||
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC);
|
||||
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter);
|
||||
|
||||
namespace internal {
|
||||
// Forward declarations of internal types.
|
||||
@ -53,6 +74,7 @@ template <typename Tag> struct ID {
|
||||
IDBuilder.AddInteger(Value);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Tag>
|
||||
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
|
||||
return OS << ID.Value;
|
||||
@ -78,7 +100,8 @@ using ProgramPoint = const Fact *;
|
||||
/// encapsulates the various dataflow analyses.
|
||||
class LifetimeSafetyAnalysis {
|
||||
public:
|
||||
LifetimeSafetyAnalysis(AnalysisDeclContext &AC);
|
||||
LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter);
|
||||
~LifetimeSafetyAnalysis();
|
||||
|
||||
void run();
|
||||
@ -87,7 +110,7 @@ public:
|
||||
LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
|
||||
|
||||
/// Returns the set of loans that have expired at a specific program point.
|
||||
LoanSet getExpiredLoansAtPoint(ProgramPoint PP) const;
|
||||
std::vector<LoanID> getExpiredLoansAtPoint(ProgramPoint PP) const;
|
||||
|
||||
/// Finds the OriginID for a given declaration.
|
||||
/// Returns a null optional if not found.
|
||||
@ -110,6 +133,7 @@ public:
|
||||
|
||||
private:
|
||||
AnalysisDeclContext &AC;
|
||||
LifetimeSafetyReporter *Reporter;
|
||||
std::unique_ptr<LifetimeFactory> Factory;
|
||||
std::unique_ptr<FactManager> FactMgr;
|
||||
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
|
||||
@ -118,4 +142,25 @@ private:
|
||||
} // namespace internal
|
||||
} // namespace clang::lifetimes
|
||||
|
||||
namespace llvm {
|
||||
template <typename Tag>
|
||||
struct DenseMapInfo<clang::lifetimes::internal::ID<Tag>> {
|
||||
using ID = clang::lifetimes::internal::ID<Tag>;
|
||||
|
||||
static inline ID getEmptyKey() {
|
||||
return {DenseMapInfo<uint32_t>::getEmptyKey()};
|
||||
}
|
||||
|
||||
static inline ID getTombstoneKey() {
|
||||
return {DenseMapInfo<uint32_t>::getTombstoneKey()};
|
||||
}
|
||||
|
||||
static unsigned getHashValue(const ID &Val) {
|
||||
return DenseMapInfo<uint32_t>::getHashValue(Val.Value);
|
||||
}
|
||||
|
||||
static bool isEqual(const ID &LHS, const ID &RHS) { return LHS == RHS; }
|
||||
};
|
||||
} // namespace llvm
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
|
||||
|
||||
@ -42,6 +42,18 @@ struct ContextSensitiveOptions {
|
||||
unsigned Depth = 2;
|
||||
};
|
||||
|
||||
/// A simple representation of essential elements of the logical context used in
|
||||
/// environments. Designed for import/export for applications requiring
|
||||
/// serialization support.
|
||||
struct SimpleLogicalContext {
|
||||
// Global invariant that applies for all definitions in the context.
|
||||
const Formula *Invariant;
|
||||
// Flow-condition tokens in the context.
|
||||
llvm::DenseMap<Atom, const Formula *> TokenDefs;
|
||||
// Dependencies between flow-condition definitions.
|
||||
llvm::DenseMap<Atom, llvm::DenseSet<Atom>> TokenDeps;
|
||||
};
|
||||
|
||||
/// Owns objects that encompass the state of a program and stores context that
|
||||
/// is used during dataflow analysis.
|
||||
class DataflowAnalysisContext {
|
||||
@ -140,6 +152,15 @@ public:
|
||||
/// Adds `Constraint` to the flow condition identified by `Token`.
|
||||
void addFlowConditionConstraint(Atom Token, const Formula &Constraint);
|
||||
|
||||
/// Adds `Deps` to the dependencies of the flow condition identified by
|
||||
/// `Token`. Intended for use in deserializing contexts. The formula alone
|
||||
/// doesn't have enough information to indicate its deps.
|
||||
void addFlowConditionDeps(Atom Token, const llvm::DenseSet<Atom> &Deps) {
|
||||
// Avoid creating an entry for `Token` with an empty set.
|
||||
if (!Deps.empty())
|
||||
FlowConditionDeps[Token].insert(Deps.begin(), Deps.end());
|
||||
}
|
||||
|
||||
/// Creates a new flow condition with the same constraints as the flow
|
||||
/// condition identified by `Token` and returns its token.
|
||||
Atom forkFlowCondition(Atom Token);
|
||||
@ -207,6 +228,14 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Export the logical-context portions of `AC`, limited to the given target
|
||||
/// flow-condition tokens.
|
||||
SimpleLogicalContext
|
||||
exportLogicalContext(llvm::DenseSet<dataflow::Atom> TargetTokens) const;
|
||||
|
||||
/// Initializes this context's "logical" components with `LC`.
|
||||
void initLogicalContext(SimpleLogicalContext LC);
|
||||
|
||||
private:
|
||||
friend class Environment;
|
||||
|
||||
@ -228,6 +257,11 @@ private:
|
||||
DataflowAnalysisContext(Solver &S, std::unique_ptr<Solver> &&OwnedSolver,
|
||||
Options Opts);
|
||||
|
||||
/// Computes the transitive closure of dependencies of (flow-condition)
|
||||
/// `Tokens`. That is, the set of flow-condition tokens reachable from
|
||||
/// `Tokens` in the dependency graph.
|
||||
llvm::DenseSet<Atom> collectDependencies(llvm::DenseSet<Atom> Tokens) const;
|
||||
|
||||
// Extends the set of modeled field declarations.
|
||||
void addModeledFields(const FieldSet &Fields);
|
||||
|
||||
|
||||
@ -157,10 +157,18 @@ public:
|
||||
};
|
||||
|
||||
/// Creates an environment that uses `DACtx` to store objects that encompass
|
||||
/// the state of a program.
|
||||
/// the state of a program. `FlowConditionToken` sets the flow condition
|
||||
/// associated with the environment. Generally, new environments should be
|
||||
/// initialized with a fresh token, by using one of the other
|
||||
/// constructors. This constructor is for specialized use, including
|
||||
/// deserialization and delegation from other constructors.
|
||||
Environment(DataflowAnalysisContext &DACtx, Atom FlowConditionToken)
|
||||
: DACtx(&DACtx), FlowConditionToken(FlowConditionToken) {}
|
||||
|
||||
/// Creates an environment that uses `DACtx` to store objects that encompass
|
||||
/// the state of a program. Populates a fresh atom as flow condition token.
|
||||
explicit Environment(DataflowAnalysisContext &DACtx)
|
||||
: DACtx(&DACtx),
|
||||
FlowConditionToken(DACtx.arena().makeFlowConditionToken()) {}
|
||||
: Environment(DACtx, DACtx.arena().makeFlowConditionToken()) {}
|
||||
|
||||
/// Creates an environment that uses `DACtx` to store objects that encompass
|
||||
/// the state of a program, with `S` as the statement to analyze.
|
||||
|
||||
@ -85,21 +85,17 @@ public:
|
||||
}
|
||||
|
||||
using AtomNames = llvm::DenseMap<Atom, std::string>;
|
||||
// Produce a stable human-readable representation of this formula.
|
||||
// For example: (V3 | !(V1 & V2))
|
||||
// If AtomNames is provided, these override the default V0, V1... names.
|
||||
/// Produces a stable human-readable representation of this formula.
|
||||
/// For example: (V3 | !(V1 & V2))
|
||||
/// If AtomNames is provided, these override the default V0, V1... names.
|
||||
void print(llvm::raw_ostream &OS, const AtomNames * = nullptr) const;
|
||||
|
||||
// Allocate Formulas using Arena rather than calling this function directly.
|
||||
/// Allocates Formulas using Arena rather than calling this function directly.
|
||||
static const Formula &create(llvm::BumpPtrAllocator &Alloc, Kind K,
|
||||
ArrayRef<const Formula *> Operands,
|
||||
unsigned Value = 0);
|
||||
|
||||
private:
|
||||
Formula() = default;
|
||||
Formula(const Formula &) = delete;
|
||||
Formula &operator=(const Formula &) = delete;
|
||||
|
||||
/// Count of operands (sub-formulas) associated with Formulas of kind `K`.
|
||||
static unsigned numOperands(Kind K) {
|
||||
switch (K) {
|
||||
case AtomRef:
|
||||
@ -116,6 +112,11 @@ private:
|
||||
llvm_unreachable("Unhandled Formula::Kind enum");
|
||||
}
|
||||
|
||||
private:
|
||||
Formula() = default;
|
||||
Formula(const Formula &) = delete;
|
||||
Formula &operator=(const Formula &) = delete;
|
||||
|
||||
Kind FormulaKind;
|
||||
// Some kinds of formula have scalar values, e.g. AtomRef's atom number.
|
||||
unsigned Value;
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
//=== FormulaSerialization.h - Formula De/Serialization support -*- 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_ANALYSIS_FLOWSENSITIVE_FORMULA_SERIALIZATION_H
|
||||
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_FORMULA_SERIALIZATION_H
|
||||
|
||||
#include "clang/Analysis/FlowSensitive/Arena.h"
|
||||
#include "clang/Analysis/FlowSensitive/Formula.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/DenseMapInfo.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
namespace clang::dataflow {
|
||||
|
||||
/// Prints `F` to `OS` in a compact format, optimized for easy parsing
|
||||
/// (deserialization) rather than human use.
|
||||
void serializeFormula(const Formula &F, llvm::raw_ostream &OS);
|
||||
|
||||
/// Parses `Str` to build a serialized Formula.
|
||||
/// @returns error on parse failure or if parsing does not fully consume `Str`.
|
||||
/// @param A used to construct the formula components.
|
||||
/// @param AtomMap maps serialized Atom identifiers (unsigned ints) to Atoms.
|
||||
/// This map is provided by the caller to enable consistency across
|
||||
/// multiple formulas in a single file.
|
||||
llvm::Expected<const Formula *>
|
||||
parseFormula(llvm::StringRef Str, Arena &A,
|
||||
llvm::DenseMap<unsigned, Atom> &AtomMap);
|
||||
|
||||
} // namespace clang::dataflow
|
||||
#endif
|
||||
@ -268,7 +268,6 @@ let Header = "emmintrin.h", Attributes = [NoThrow, RequireDeclaration] in {
|
||||
}
|
||||
|
||||
let Features = "sse2", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
def pmuludq128 : X86Builtin<"_Vector<2, long long int>(_Vector<4, int>, _Vector<4, int>)">;
|
||||
def psraw128 : X86Builtin<"_Vector<8, short>(_Vector<8, short>, _Vector<8, short>)">;
|
||||
def psrad128 : X86Builtin<"_Vector<4, int>(_Vector<4, int>, _Vector<4, int>)">;
|
||||
def psrlw128 : X86Builtin<"_Vector<8, short>(_Vector<8, short>, _Vector<8, short>)">;
|
||||
@ -290,6 +289,10 @@ let Features = "sse2", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] i
|
||||
def psrldqi128_byteshift : X86Builtin<"_Vector<2, long long int>(_Vector<2, long long int>, _Constant int)">;
|
||||
}
|
||||
|
||||
let Features = "sse2", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def pmuludq128 : X86Builtin<"_Vector<2, long long int>(_Vector<4, int>, _Vector<4, int>)">;
|
||||
}
|
||||
|
||||
let Features = "sse3", Attributes = [NoThrow] in {
|
||||
def monitor : X86Builtin<"void(void const *, unsigned int, unsigned int)">;
|
||||
def mwait : X86Builtin<"void(unsigned int, unsigned int)">;
|
||||
@ -312,7 +315,6 @@ let Features = "sse4.1", Attributes = [NoThrow, Const, RequiredVectorWidth<128>]
|
||||
def blendvpd : X86Builtin<"_Vector<2, double>(_Vector<2, double>, _Vector<2, double>, _Vector<2, double>)">;
|
||||
def blendvps : X86Builtin<"_Vector<4, float>(_Vector<4, float>, _Vector<4, float>, _Vector<4, float>)">;
|
||||
def packusdw128 : X86Builtin<"_Vector<8, short>(_Vector<4, int>, _Vector<4, int>)">;
|
||||
def pmuldq128 : X86Builtin<"_Vector<2, long long int>(_Vector<4, int>, _Vector<4, int>)">;
|
||||
def roundps : X86Builtin<"_Vector<4, float>(_Vector<4, float>, _Constant int)">;
|
||||
def roundss : X86Builtin<"_Vector<4, float>(_Vector<4, float>, _Vector<4, float>, _Constant int)">;
|
||||
def roundsd : X86Builtin<"_Vector<2, double>(_Vector<2, double>, _Vector<2, double>, _Constant int)">;
|
||||
@ -329,6 +331,10 @@ let Features = "sse4.1", Attributes = [NoThrow, Const, RequiredVectorWidth<128>]
|
||||
def vec_set_v4si : X86Builtin<"_Vector<4, int>(_Vector<4, int>, int, _Constant int)">;
|
||||
}
|
||||
|
||||
let Features = "sse4.1", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def pmuldq128 : X86Builtin<"_Vector<2, long long int>(_Vector<4, int>, _Vector<4, int>)">;
|
||||
}
|
||||
|
||||
let Features = "sse4.2", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
def pcmpistrm128 : X86Builtin<"_Vector<16, char>(_Vector<16, char>, _Vector<16, char>, _Constant char)">;
|
||||
def pcmpistri128 : X86Builtin<"int(_Vector<16, char>, _Vector<16, char>, _Constant char)">;
|
||||
@ -580,9 +586,7 @@ let Features = "avx2", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] i
|
||||
def pmaddubsw256 : X86Builtin<"_Vector<16, short>(_Vector<32, char>, _Vector<32, char>)">;
|
||||
def pmaddwd256 : X86Builtin<"_Vector<8, int>(_Vector<16, short>, _Vector<16, short>)">;
|
||||
def pmovmskb256 : X86Builtin<"int(_Vector<32, char>)">;
|
||||
def pmuldq256 : X86Builtin<"_Vector<4, long long int>(_Vector<8, int>, _Vector<8, int>)">;
|
||||
def pmulhrsw256 : X86Builtin<"_Vector<16, short>(_Vector<16, short>, _Vector<16, short>)">;
|
||||
def pmuludq256 : X86Builtin<"_Vector<4, long long int>(_Vector<8, int>, _Vector<8, int>)">;
|
||||
def psadbw256 : X86Builtin<"_Vector<4, long long int>(_Vector<32, char>, _Vector<32, char>)">;
|
||||
def pshufb256 : X86Builtin<"_Vector<32, char>(_Vector<32, char>, _Vector<32, char>)">;
|
||||
def pshufd256 : X86Builtin<"_Vector<8, int>(_Vector<8, int>, _Constant int)">;
|
||||
@ -620,6 +624,11 @@ let Features = "avx2", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] i
|
||||
def insert128i256 : X86Builtin<"_Vector<4, long long int>(_Vector<4, long long int>, _Vector<2, long long int>, _Constant int)">;
|
||||
}
|
||||
|
||||
let Features = "avx2", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def pmuldq256 : X86Builtin<"_Vector<4, long long int>(_Vector<8, int>, _Vector<8, int>)">;
|
||||
def pmuludq256 : X86Builtin<"_Vector<4, long long int>(_Vector<8, int>, _Vector<8, int>)">;
|
||||
}
|
||||
|
||||
let Features = "avx2", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def pmulhuw256 : X86Builtin<"_Vector<16, unsigned short>(_Vector<16, unsigned short>, _Vector<16, unsigned short>)">;
|
||||
def pmulhw256 : X86Builtin<"_Vector<16, short>(_Vector<16, short>, _Vector<16, short>)">;
|
||||
@ -1078,6 +1087,9 @@ let Features = "avx512f,evex512", Attributes = [NoThrow, Const, RequiredVectorWi
|
||||
def cvtpd2ps512_mask : X86Builtin<"_Vector<8, float>(_Vector<8, double>, _Vector<8, float>, unsigned char, _Constant int)">;
|
||||
def vcvtps2ph512_mask : X86Builtin<"_Vector<16, short>(_Vector<16, float>, _Constant int, _Vector<16, short>, unsigned short)">;
|
||||
def vcvtph2ps512_mask : X86Builtin<"_Vector<16, float>(_Vector<16, short>, _Vector<16, float>, unsigned short, _Constant int)">;
|
||||
}
|
||||
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def pmuldq512 : X86Builtin<"_Vector<8, long long int>(_Vector<16, int>, _Vector<16, int>)">;
|
||||
def pmuludq512 : X86Builtin<"_Vector<8, long long int>(_Vector<16, int>, _Vector<16, int>)">;
|
||||
}
|
||||
@ -4118,99 +4130,99 @@ let Features = "avx512fp16,evex512", Attributes = [NoThrow, Const, RequiredVecto
|
||||
def vfcmulcph512_mask : X86Builtin<"_Vector<16, float>(_Vector<16, float>, _Vector<16, float>, _Vector<16, float>, unsigned short, _Constant int)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectb_128 : X86Builtin<"_Vector<16, char>(unsigned short, _Vector<16, char>, _Vector<16, char>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectb_256 : X86Builtin<"_Vector<32, char>(unsigned int, _Vector<32, char>, _Vector<32, char>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bw,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512bw,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectb_512 : X86Builtin<"_Vector<64, char>(unsigned long long int, _Vector<64, char>, _Vector<64, char>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectw_128 : X86Builtin<"_Vector<8, short>(unsigned char, _Vector<8, short>, _Vector<8, short>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512bw,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectw_256 : X86Builtin<"_Vector<16, short>(unsigned short, _Vector<16, short>, _Vector<16, short>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bw,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512bw,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectw_512 : X86Builtin<"_Vector<32, short>(unsigned int, _Vector<32, short>, _Vector<32, short>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectd_128 : X86Builtin<"_Vector<4, int>(unsigned char, _Vector<4, int>, _Vector<4, int>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectd_256 : X86Builtin<"_Vector<8, int>(unsigned char, _Vector<8, int>, _Vector<8, int>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectd_512 : X86Builtin<"_Vector<16, int>(unsigned short, _Vector<16, int>, _Vector<16, int>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512fp16,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512fp16,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectph_128 : X86Builtin<"_Vector<8, _Float16>(unsigned char, _Vector<8, _Float16>, _Vector<8, _Float16>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512fp16,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512fp16,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectph_256 : X86Builtin<"_Vector<16, _Float16>(unsigned short, _Vector<16, _Float16>, _Vector<16, _Float16>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512fp16,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512fp16,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectph_512 : X86Builtin<"_Vector<32, _Float16>(unsigned int, _Vector<32, _Float16>, _Vector<32, _Float16>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bf16,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512bf16,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectpbf_128 : X86Builtin<"_Vector<8, __bf16>(unsigned char, _Vector<8, __bf16>, _Vector<8, __bf16>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bf16,avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512bf16,avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectpbf_256 : X86Builtin<"_Vector<16, __bf16>(unsigned short, _Vector<16, __bf16>, _Vector<16, __bf16>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512bf16,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512bf16,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectpbf_512 : X86Builtin<"_Vector<32, __bf16>(unsigned int, _Vector<32, __bf16>, _Vector<32, __bf16>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectq_128 : X86Builtin<"_Vector<2, long long int>(unsigned char, _Vector<2, long long int>, _Vector<2, long long int>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectq_256 : X86Builtin<"_Vector<4, long long int>(unsigned char, _Vector<4, long long int>, _Vector<4, long long int>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectq_512 : X86Builtin<"_Vector<8, long long int>(unsigned char, _Vector<8, long long int>, _Vector<8, long long int>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectps_128 : X86Builtin<"_Vector<4, float>(unsigned char, _Vector<4, float>, _Vector<4, float>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectps_256 : X86Builtin<"_Vector<8, float>(unsigned char, _Vector<8, float>, _Vector<8, float>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectps_512 : X86Builtin<"_Vector<16, float>(unsigned short, _Vector<16, float>, _Vector<16, float>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<128>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<128>] in {
|
||||
def selectpd_128 : X86Builtin<"_Vector<2, double>(unsigned char, _Vector<2, double>, _Vector<2, double>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, RequiredVectorWidth<256>] in {
|
||||
let Features = "avx512vl", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<256>] in {
|
||||
def selectpd_256 : X86Builtin<"_Vector<4, double>(unsigned char, _Vector<4, double>, _Vector<4, double>)">;
|
||||
}
|
||||
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, RequiredVectorWidth<512>] in {
|
||||
let Features = "avx512f,evex512", Attributes = [NoThrow, Const, Constexpr, RequiredVectorWidth<512>] in {
|
||||
def selectpd_512 : X86Builtin<"_Vector<8, double>(unsigned char, _Vector<8, double>, _Vector<8, double>)">;
|
||||
}
|
||||
|
||||
|
||||
@ -533,7 +533,14 @@ def Dangling : DiagGroup<"dangling", [DanglingAssignment,
|
||||
DanglingGsl,
|
||||
ReturnStackAddress]>;
|
||||
|
||||
def LifetimeSafety : DiagGroup<"experimental-lifetime-safety">;
|
||||
def LifetimeSafetyPermissive : DiagGroup<"experimental-lifetime-safety-permissive">;
|
||||
def LifetimeSafetyStrict : DiagGroup<"experimental-lifetime-safety-strict">;
|
||||
def LifetimeSafety : DiagGroup<"experimental-lifetime-safety",
|
||||
[LifetimeSafetyPermissive, LifetimeSafetyStrict]> {
|
||||
code Documentation = [{
|
||||
Experimental warnings to detect use-after-free and related temporal safety bugs based on lifetime safety analysis.
|
||||
}];
|
||||
}
|
||||
|
||||
def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">;
|
||||
def DllexportExplicitInstantiationDecl : DiagGroup<"dllexport-explicit-instantiation-decl">;
|
||||
|
||||
@ -10671,9 +10671,15 @@ def warn_dangling_reference_captured_by_unknown : Warning<
|
||||
"object whose reference is captured will be destroyed at the end of "
|
||||
"the full-expression">, InGroup<DanglingCapture>;
|
||||
|
||||
def warn_experimental_lifetime_safety_dummy_warning : Warning<
|
||||
"todo: remove this warning after we have atleast one warning based on the lifetime analysis">,
|
||||
InGroup<LifetimeSafety>, DefaultIgnore;
|
||||
// Diagnostics based on the Lifetime safety analysis.
|
||||
def warn_lifetime_safety_loan_expires_permissive : Warning<
|
||||
"object whose reference is captured does not live long enough">,
|
||||
InGroup<LifetimeSafetyPermissive>, DefaultIgnore;
|
||||
def warn_lifetime_safety_loan_expires_strict : Warning<
|
||||
"object whose reference is captured may not live long enough">,
|
||||
InGroup<LifetimeSafetyStrict>, DefaultIgnore;
|
||||
def note_lifetime_safety_used_here : Note<"later used here">;
|
||||
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
|
||||
|
||||
// For non-floating point, expressions of the form x == x or x != x
|
||||
// should result in a warning, since these always evaluate to a constant.
|
||||
|
||||
@ -233,8 +233,9 @@ protected:
|
||||
bool TLSSupported;
|
||||
bool VLASupported;
|
||||
bool NoAsmVariants; // True if {|} are normal characters.
|
||||
bool HasLegalHalfType; // True if the backend supports operations on the half
|
||||
// LLVM IR type.
|
||||
bool HasFastHalfType; // True if the backend has native half float support,
|
||||
// and performing calculations in float instead does
|
||||
// not have a performance advantage.
|
||||
bool HalfArgsAndReturns; // OpenCL 6.1.1.1, NEON (IEEE 754-2008 half) type.
|
||||
bool HasFloat128;
|
||||
bool HasFloat16;
|
||||
@ -700,8 +701,9 @@ public:
|
||||
return 128;
|
||||
}
|
||||
|
||||
/// Determine whether _Float16 is supported on this target.
|
||||
virtual bool hasLegalHalfType() const { return HasLegalHalfType; }
|
||||
/// Determine whether the target has fast native support for operations
|
||||
/// on half types.
|
||||
virtual bool hasFastHalfType() const { return HasFastHalfType; }
|
||||
|
||||
/// Whether half args and returns are supported.
|
||||
virtual bool allowHalfArgsAndReturns() const { return HalfArgsAndReturns; }
|
||||
|
||||
@ -504,8 +504,7 @@ public:
|
||||
static OpBuilder::InsertPoint getBestAllocaInsertPoint(mlir::Block *block) {
|
||||
auto last =
|
||||
std::find_if(block->rbegin(), block->rend(), [](mlir::Operation &op) {
|
||||
// TODO: Add LabelOp missing feature here
|
||||
return mlir::isa<cir::AllocaOp>(&op);
|
||||
return mlir::isa<cir::AllocaOp, cir::LabelOp>(&op);
|
||||
});
|
||||
|
||||
if (last != block->rend())
|
||||
|
||||
@ -1060,6 +1060,62 @@ def CIR_BrOp : CIR_Op<"br",[
|
||||
}];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// GotoOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
def CIR_GotoOp : CIR_Op<"goto", [Terminator]> {
|
||||
let description = [{
|
||||
|
||||
Transfers control to the specified `label`. This requires a corresponding
|
||||
`cir.label` to exist and is used by to represent source level `goto`s
|
||||
that jump across region boundaries. Alternatively, `cir.br` is used to
|
||||
construct goto's that don't violate such boundaries.
|
||||
|
||||
`cir.goto` is completely symbolic (i.e. it "jumps" on a label that isn't
|
||||
yet materialized) and should be taken into account by passes and analysis
|
||||
when deciding if it's safe to make some assumptions about a given region
|
||||
or basic block.
|
||||
|
||||
Example:
|
||||
```C++
|
||||
int test(int x) {
|
||||
if (x)
|
||||
goto label;
|
||||
{
|
||||
x = 10;
|
||||
label:
|
||||
return x;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mlir
|
||||
cir.scope { // REGION #1
|
||||
%2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
|
||||
%3 = cir.cast(int_to_bool, %2 : !s32i), !cir.bool
|
||||
cir.if %3 {
|
||||
cir.goto "label"
|
||||
}
|
||||
}
|
||||
cir.scope { // REGION #2
|
||||
%2 = cir.const #cir.int<10> : !s32i
|
||||
cir.store %2, %0 : !s32i, !cir.ptr<!s32i>
|
||||
cir.br ^bb1
|
||||
^bb1: // pred: ^bb0
|
||||
cir.label "label"
|
||||
%3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
|
||||
cir.store %3, %1 : !s32i, !cir.ptr<!s32i>
|
||||
%4 = cir.load %1 : !cir.ptr<!s32i>, !s32i
|
||||
cir.return %4 : !s32i
|
||||
}
|
||||
cir.unreachable
|
||||
```
|
||||
}];
|
||||
let arguments = (ins StrAttr:$label);
|
||||
let assemblyFormat = [{ $label attr-dict }];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// LabelOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
@ -1749,6 +1805,39 @@ def CIR_VTableAddrPointOp : CIR_Op<"vtable.address_point", [
|
||||
}];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VTableGetVPtr
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
def CIR_VTableGetVPtrOp : CIR_Op<"vtable.get_vptr", [Pure]> {
|
||||
let summary = "Get a the address of the vtable pointer for an object";
|
||||
let description = [{
|
||||
The `vtable.get_vptr` operation retrieves the address of the vptr for a
|
||||
C++ object. This operation requires that the object pointer points to
|
||||
the start of a complete object. (TODO: Describe how we get that).
|
||||
The vptr will always be at offset zero in the object, but this operation
|
||||
is more explicit about what is being retrieved than a direct bitcast.
|
||||
|
||||
The return type is always `!cir.ptr<!cir.vptr>`.
|
||||
|
||||
Example:
|
||||
```mlir
|
||||
%2 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
|
||||
%3 = cir.vtable.get_vptr %2 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
|
||||
```
|
||||
}];
|
||||
|
||||
let arguments = (ins
|
||||
Arg<CIR_PointerType, "the vptr address", [MemRead]>:$src
|
||||
);
|
||||
|
||||
let results = (outs CIR_PtrToVPtr:$result);
|
||||
|
||||
let assemblyFormat = [{
|
||||
$src `:` qualified(type($src)) `->` qualified(type($result)) attr-dict
|
||||
}];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// SetBitfieldOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
@ -2210,6 +2299,68 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
|
||||
];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// ReturnAddrOp and FrameAddrOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
class CIR_FuncAddrBuiltinOp<string mnemonic> : CIR_Op<mnemonic, []> {
|
||||
let arguments = (ins CIR_UInt32:$level);
|
||||
let results = (outs CIR_VoidPtrType:$result);
|
||||
let assemblyFormat = [{
|
||||
`(` $level `)` attr-dict
|
||||
}];
|
||||
}
|
||||
|
||||
def CIR_ReturnAddrOp : CIR_FuncAddrBuiltinOp<"return_address"> {
|
||||
let summary =
|
||||
"The return address of the current function, or of one of its callers";
|
||||
|
||||
let description = [{
|
||||
Represents a call to builtin function ` __builtin_return_address` in CIR.
|
||||
This builtin function returns the return address of the current function,
|
||||
or of one of its callers.
|
||||
|
||||
The `level` argument is number of frames to scan up the call stack.
|
||||
For instance, value of 0 yields the return address of the current function,
|
||||
value of 1 yields the return address of the caller of the current function,
|
||||
and so forth.
|
||||
|
||||
Examples:
|
||||
|
||||
```mlir
|
||||
%p = return_address(%level) -> !cir.ptr<!void>
|
||||
```
|
||||
}];
|
||||
}
|
||||
|
||||
def CIR_FrameAddrOp : CIR_FuncAddrBuiltinOp<"frame_address"> {
|
||||
let summary =
|
||||
"The frame address of the current function, or of one of its callers";
|
||||
|
||||
let description = [{
|
||||
Represents a call to builtin function ` __builtin_frame_address` in CIR.
|
||||
This builtin function returns the frame address of the current function,
|
||||
or of one of its callers. The frame is the area on the stack that holds
|
||||
local variables and saved registers. The frame address is normally the
|
||||
address of the first word pushed on to the stack by the function.
|
||||
However, the exact definition depends upon the processor and the calling
|
||||
convention. If the processor has a dedicated frame pointer register, and
|
||||
the function has a frame, then __builtin_frame_address returns the value of
|
||||
the frame pointer register.
|
||||
|
||||
The `level` argument is number of frames to scan up the call stack.
|
||||
For instance, value of 0 yields the frame address of the current function,
|
||||
value of 1 yields the frame address of the caller of the current function,
|
||||
and so forth.
|
||||
|
||||
Examples:
|
||||
|
||||
```mlir
|
||||
%p = frame_address(%level) -> !cir.ptr<!void>
|
||||
```
|
||||
}];
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// StackSaveOp & StackRestoreOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@ -289,6 +289,14 @@ def CIR_AnyFloatOrVecOfFloatType
|
||||
let cppFunctionName = "isFPOrVectorOfFPType";
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VPtr type predicates
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
def CIR_AnyVPtrType : CIR_TypeBase<"::cir::VPtrType", "vptr type">;
|
||||
|
||||
def CIR_PtrToVPtr : CIR_PtrToType<CIR_AnyVPtrType>;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Scalar Type predicates
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@ -296,10 +296,10 @@ def CIR_VPtrType : CIR_Type<"VPtr", "vptr", [
|
||||
access to the vptr.
|
||||
|
||||
This type will be the element type of the 'vptr' member of structures that
|
||||
require a vtable pointer. A pointer to this type is returned by the
|
||||
`cir.vtable.address_point` and `cir.vtable.get_vptr` operations, and this
|
||||
pointer may be passed to the `cir.vtable.get_virtual_fn_addr` operation to
|
||||
get the address of a virtual function pointer.
|
||||
require a vtable pointer. The `cir.vtable.address_point` operation returns
|
||||
this type. The `cir.vtable.get_vptr` operations returns a pointer to this
|
||||
type. This pointer may be passed to the `cir.vtable.get_virtual_fn_addr`
|
||||
operation to get the address of a virtual function pointer.
|
||||
|
||||
The pointer may also be cast to other pointer types in order to perform
|
||||
pointer arithmetic based on information encoded in the AST layout to get
|
||||
|
||||
@ -6987,7 +6987,6 @@ def static_libgfortran : Flag<["-"], "static-libgfortran">, Group<gfortran_Group
|
||||
// "f" options with values for gfortran.
|
||||
def fblas_matmul_limit_EQ : Joined<["-"], "fblas-matmul-limit=">, Group<gfortran_Group>;
|
||||
def fcheck_EQ : Joined<["-"], "fcheck=">, Group<gfortran_Group>;
|
||||
def fcoarray_EQ : Joined<["-"], "fcoarray=">, Group<gfortran_Group>;
|
||||
def ffpe_trap_EQ : Joined<["-"], "ffpe-trap=">, Group<gfortran_Group>;
|
||||
def ffree_line_length_VALUE : Joined<["-"], "ffree-line-length-">, Group<gfortran_Group>;
|
||||
def finit_character_EQ : Joined<["-"], "finit-character=">, Group<gfortran_Group>;
|
||||
@ -8695,6 +8694,15 @@ def fopenmp_host_ir_file_path : Separate<["-"], "fopenmp-host-ir-file-path">,
|
||||
|
||||
} // let Visibility = [CC1Option, FC1Option]
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Coarray Options
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
def fcoarray : Flag<["-"], "fcoarray">,
|
||||
Group<f_Group>,
|
||||
Visibility<[FlangOption, FC1Option]>,
|
||||
HelpText<"Enable Coarray features">;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// SYCL Options
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@ -4813,14 +4813,45 @@ struct FormatStyle {
|
||||
/// \version 7
|
||||
bool SpaceBeforeRangeBasedForLoopColon;
|
||||
|
||||
/// If ``true``, spaces will be inserted into ``{}``.
|
||||
/// \code
|
||||
/// true: false:
|
||||
/// void f() { } vs. void f() {}
|
||||
/// while (true) { } while (true) {}
|
||||
/// \endcode
|
||||
/// This option is **deprecated**. See ``Block`` of ``SpaceInEmptyBraces``.
|
||||
/// \version 10
|
||||
bool SpaceInEmptyBlock;
|
||||
// bool SpaceInEmptyBlock;
|
||||
|
||||
/// Style of when to insert a space in empty braces.
|
||||
enum SpaceInEmptyBracesStyle : int8_t {
|
||||
/// Always insert a space in empty braces.
|
||||
/// \code
|
||||
/// void f() { }
|
||||
/// class Unit { };
|
||||
/// auto a = [] { };
|
||||
/// int x{ };
|
||||
/// \endcode
|
||||
SIEB_Always,
|
||||
/// Only insert a space in empty blocks.
|
||||
/// \code
|
||||
/// void f() { }
|
||||
/// class Unit { };
|
||||
/// auto a = [] { };
|
||||
/// int x{};
|
||||
/// \endcode
|
||||
SIEB_Block,
|
||||
/// Never insert a space in empty braces.
|
||||
/// \code
|
||||
/// void f() {}
|
||||
/// class Unit {};
|
||||
/// auto a = [] {};
|
||||
/// int x{};
|
||||
/// \endcode
|
||||
SIEB_Never
|
||||
};
|
||||
|
||||
/// Specifies when to insert a space in empty braces.
|
||||
/// \note
|
||||
/// This option doesn't apply to initializer braces if
|
||||
/// ``Cpp11BracedListStyle`` is set to ``true``.
|
||||
/// \endnote
|
||||
/// \version 22
|
||||
SpaceInEmptyBracesStyle SpaceInEmptyBraces;
|
||||
|
||||
/// If ``true``, spaces may be inserted into ``()``.
|
||||
/// This option is **deprecated**. See ``InEmptyParentheses`` of
|
||||
@ -5494,7 +5525,7 @@ struct FormatStyle {
|
||||
SpaceBeforeRangeBasedForLoopColon ==
|
||||
R.SpaceBeforeRangeBasedForLoopColon &&
|
||||
SpaceBeforeSquareBrackets == R.SpaceBeforeSquareBrackets &&
|
||||
SpaceInEmptyBlock == R.SpaceInEmptyBlock &&
|
||||
SpaceInEmptyBraces == R.SpaceInEmptyBraces &&
|
||||
SpacesBeforeTrailingComments == R.SpacesBeforeTrailingComments &&
|
||||
SpacesInAngles == R.SpacesInAngles &&
|
||||
SpacesInContainerLiterals == R.SpacesInContainerLiterals &&
|
||||
|
||||
@ -143,9 +143,6 @@ class Lexer : public PreprocessorLexer {
|
||||
/// True if this is the first time we're lexing the input file.
|
||||
bool IsFirstTimeLexingFile;
|
||||
|
||||
/// True if current lexing token is the first pp-token.
|
||||
bool IsFirstPPToken;
|
||||
|
||||
// NewLinePtr - A pointer to new line character '\n' being lexed. For '\r\n',
|
||||
// it also points to '\n.'
|
||||
const char *NewLinePtr;
|
||||
|
||||
310
clang/include/clang/Lex/NoTrivialPPDirectiveTracer.h
Normal file
310
clang/include/clang/Lex/NoTrivialPPDirectiveTracer.h
Normal file
@ -0,0 +1,310 @@
|
||||
//===--- NoTrivialPPDirectiveTracer.h ---------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file defines the NoTrivialPPDirectiveTracer interface.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_LEX_NO_TRIVIAL_PPDIRECTIVE_TRACER_H
|
||||
#define LLVM_CLANG_LEX_NO_TRIVIAL_PPDIRECTIVE_TRACER_H
|
||||
|
||||
#include "clang/Lex/PPCallbacks.h"
|
||||
|
||||
namespace clang {
|
||||
class Preprocessor;
|
||||
|
||||
/// Consider the following code:
|
||||
///
|
||||
/// # 1 __FILE__ 1 3
|
||||
/// export module a;
|
||||
///
|
||||
/// According to the wording in
|
||||
/// [P1857R3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1857r3.html):
|
||||
///
|
||||
/// A module directive may only appear as the first preprocessing tokens in a
|
||||
/// file (excluding the global module fragment.)
|
||||
///
|
||||
/// and the wording in
|
||||
/// [[cpp.pre]](https://eel.is/c++draft/cpp.pre#nt:module-file):
|
||||
/// module-file:
|
||||
/// pp-global-module-fragment[opt] pp-module group[opt]
|
||||
/// pp-private-module-fragment[opt]
|
||||
///
|
||||
/// `#` is the first pp-token in the translation unit, and it was rejected by
|
||||
/// clang, but they really should be exempted from this rule. The goal is to not
|
||||
/// allow any preprocessor conditionals or most state changes, but these don't
|
||||
/// fit that.
|
||||
///
|
||||
/// State change would mean most semantically observable preprocessor state,
|
||||
/// particularly anything that is order dependent. Global flags like being a
|
||||
/// system header/module shouldn't matter.
|
||||
///
|
||||
/// We should exempt a brunch of directives, even though it violates the current
|
||||
/// standard wording.
|
||||
///
|
||||
/// This class used to trace 'no-trivial' pp-directives in main file, which may
|
||||
/// change the preprocessing state.
|
||||
///
|
||||
/// FIXME: Once the wording of the standard is revised, we need to follow the
|
||||
/// wording of the standard. Currently this is just a workaround
|
||||
class NoTrivialPPDirectiveTracer : public PPCallbacks {
|
||||
Preprocessor &PP;
|
||||
|
||||
/// Whether preprocessing main file. We only focus on the main file.
|
||||
bool InMainFile = true;
|
||||
|
||||
/// Whether one or more conditional, include or other 'no-trivial'
|
||||
/// pp-directives has seen before.
|
||||
bool SeenNoTrivialPPDirective = false;
|
||||
|
||||
void setSeenNoTrivialPPDirective();
|
||||
|
||||
public:
|
||||
NoTrivialPPDirectiveTracer(Preprocessor &P) : PP(P) {}
|
||||
|
||||
bool hasSeenNoTrivialPPDirective() const;
|
||||
|
||||
/// Callback invoked whenever the \p Lexer moves to a different file for
|
||||
/// lexing. Unlike \p FileChanged line number directives and other related
|
||||
/// pragmas do not trigger callbacks to \p LexedFileChanged.
|
||||
///
|
||||
/// \param FID The \p FileID that the \p Lexer moved to.
|
||||
///
|
||||
/// \param Reason Whether the \p Lexer entered a new file or exited one.
|
||||
///
|
||||
/// \param FileType The \p CharacteristicKind of the file the \p Lexer moved
|
||||
/// to.
|
||||
///
|
||||
/// \param PrevFID The \p FileID the \p Lexer was using before the change.
|
||||
///
|
||||
/// \param Loc The location where the \p Lexer entered a new file from or the
|
||||
/// location that the \p Lexer moved into after exiting a file.
|
||||
void LexedFileChanged(FileID FID, LexedFileChangeReason Reason,
|
||||
SrcMgr::CharacteristicKind FileType, FileID PrevFID,
|
||||
SourceLocation Loc) override;
|
||||
|
||||
/// Callback invoked whenever an embed directive has been processed,
|
||||
/// regardless of whether the embed will actually find a file.
|
||||
///
|
||||
/// \param HashLoc The location of the '#' that starts the embed directive.
|
||||
///
|
||||
/// \param FileName The name of the file being included, as written in the
|
||||
/// source code.
|
||||
///
|
||||
/// \param IsAngled Whether the file name was enclosed in angle brackets;
|
||||
/// otherwise, it was enclosed in quotes.
|
||||
///
|
||||
/// \param File The actual file that may be included by this embed directive.
|
||||
///
|
||||
/// \param Params The parameters used by the directive.
|
||||
void EmbedDirective(SourceLocation HashLoc, StringRef FileName, bool IsAngled,
|
||||
OptionalFileEntryRef File,
|
||||
const LexEmbedParametersResult &Params) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Callback invoked whenever an inclusion directive of
|
||||
/// any kind (\c \#include, \c \#import, etc.) has been processed, regardless
|
||||
/// of whether the inclusion will actually result in an inclusion.
|
||||
///
|
||||
/// \param HashLoc The location of the '#' that starts the inclusion
|
||||
/// directive.
|
||||
///
|
||||
/// \param IncludeTok The token that indicates the kind of inclusion
|
||||
/// directive, e.g., 'include' or 'import'.
|
||||
///
|
||||
/// \param FileName The name of the file being included, as written in the
|
||||
/// source code.
|
||||
///
|
||||
/// \param IsAngled Whether the file name was enclosed in angle brackets;
|
||||
/// otherwise, it was enclosed in quotes.
|
||||
///
|
||||
/// \param FilenameRange The character range of the quotes or angle brackets
|
||||
/// for the written file name.
|
||||
///
|
||||
/// \param File The actual file that may be included by this inclusion
|
||||
/// directive.
|
||||
///
|
||||
/// \param SearchPath Contains the search path which was used to find the file
|
||||
/// in the file system. If the file was found via an absolute include path,
|
||||
/// SearchPath will be empty. For framework includes, the SearchPath and
|
||||
/// RelativePath will be split up. For example, if an include of "Some/Some.h"
|
||||
/// is found via the framework path
|
||||
/// "path/to/Frameworks/Some.framework/Headers/Some.h", SearchPath will be
|
||||
/// "path/to/Frameworks/Some.framework/Headers" and RelativePath will be
|
||||
/// "Some.h".
|
||||
///
|
||||
/// \param RelativePath The path relative to SearchPath, at which the include
|
||||
/// file was found. This is equal to FileName except for framework includes.
|
||||
///
|
||||
/// \param SuggestedModule The module suggested for this header, if any.
|
||||
///
|
||||
/// \param ModuleImported Whether this include was translated into import of
|
||||
/// \p SuggestedModule.
|
||||
///
|
||||
/// \param FileType The characteristic kind, indicates whether a file or
|
||||
/// directory holds normal user code, system code, or system code which is
|
||||
/// implicitly 'extern "C"' in C++ mode.
|
||||
///
|
||||
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
|
||||
StringRef FileName, bool IsAngled,
|
||||
CharSourceRange FilenameRange,
|
||||
OptionalFileEntryRef File, StringRef SearchPath,
|
||||
StringRef RelativePath, const Module *SuggestedModule,
|
||||
bool ModuleImported,
|
||||
SrcMgr::CharacteristicKind FileType) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Callback invoked whenever there was an explicit module-import
|
||||
/// syntax.
|
||||
///
|
||||
/// \param ImportLoc The location of import directive token.
|
||||
///
|
||||
/// \param Path The identifiers (and their locations) of the module
|
||||
/// "path", e.g., "std.vector" would be split into "std" and "vector".
|
||||
///
|
||||
/// \param Imported The imported module; can be null if importing failed.
|
||||
///
|
||||
void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path,
|
||||
const Module *Imported) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Callback invoked when the end of the main file is reached.
|
||||
///
|
||||
/// No subsequent callbacks will be made.
|
||||
void EndOfMainFile() override { setSeenNoTrivialPPDirective(); }
|
||||
|
||||
/// Callback invoked when start reading any pragma directive.
|
||||
void PragmaDirective(SourceLocation Loc,
|
||||
PragmaIntroducerKind Introducer) override {}
|
||||
|
||||
/// Called by Preprocessor::HandleMacroExpandedIdentifier when a
|
||||
/// macro invocation is found.
|
||||
void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
|
||||
SourceRange Range, const MacroArgs *Args) override;
|
||||
|
||||
/// Hook called whenever a macro definition is seen.
|
||||
void MacroDefined(const Token &MacroNameTok,
|
||||
const MacroDirective *MD) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever a macro \#undef is seen.
|
||||
/// \param MacroNameTok The active Token
|
||||
/// \param MD A MacroDefinition for the named macro.
|
||||
/// \param Undef New MacroDirective if the macro was defined, null otherwise.
|
||||
///
|
||||
/// MD is released immediately following this callback.
|
||||
void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,
|
||||
const MacroDirective *Undef) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever the 'defined' operator is seen.
|
||||
/// \param MD The MacroDirective if the name was a macro, null otherwise.
|
||||
void Defined(const Token &MacroNameTok, const MacroDefinition &MD,
|
||||
SourceRange Range) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#if is seen.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param ConditionRange The SourceRange of the expression being tested.
|
||||
/// \param ConditionValue The evaluated value of the condition.
|
||||
///
|
||||
// FIXME: better to pass in a list (or tree!) of Tokens.
|
||||
void If(SourceLocation Loc, SourceRange ConditionRange,
|
||||
ConditionValueKind ConditionValue) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#elif is seen.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param ConditionRange The SourceRange of the expression being tested.
|
||||
/// \param ConditionValue The evaluated value of the condition.
|
||||
/// \param IfLoc the source location of the \#if/\#ifdef/\#ifndef directive.
|
||||
// FIXME: better to pass in a list (or tree!) of Tokens.
|
||||
void Elif(SourceLocation Loc, SourceRange ConditionRange,
|
||||
ConditionValueKind ConditionValue, SourceLocation IfLoc) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#ifdef is seen.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param MacroNameTok Information on the token being tested.
|
||||
/// \param MD The MacroDefinition if the name was a macro, null otherwise.
|
||||
void Ifdef(SourceLocation Loc, const Token &MacroNameTok,
|
||||
const MacroDefinition &MD) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#elifdef branch is taken.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param MacroNameTok Information on the token being tested.
|
||||
/// \param MD The MacroDefinition if the name was a macro, null otherwise.
|
||||
void Elifdef(SourceLocation Loc, const Token &MacroNameTok,
|
||||
const MacroDefinition &MD) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
/// Hook called whenever an \#elifdef is skipped.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param ConditionRange The SourceRange of the expression being tested.
|
||||
/// \param IfLoc the source location of the \#if/\#ifdef/\#ifndef directive.
|
||||
// FIXME: better to pass in a list (or tree!) of Tokens.
|
||||
void Elifdef(SourceLocation Loc, SourceRange ConditionRange,
|
||||
SourceLocation IfLoc) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#ifndef is seen.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param MacroNameTok Information on the token being tested.
|
||||
/// \param MD The MacroDefiniton if the name was a macro, null otherwise.
|
||||
void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
|
||||
const MacroDefinition &MD) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#elifndef branch is taken.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param MacroNameTok Information on the token being tested.
|
||||
/// \param MD The MacroDefinition if the name was a macro, null otherwise.
|
||||
void Elifndef(SourceLocation Loc, const Token &MacroNameTok,
|
||||
const MacroDefinition &MD) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
/// Hook called whenever an \#elifndef is skipped.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param ConditionRange The SourceRange of the expression being tested.
|
||||
/// \param IfLoc the source location of the \#if/\#ifdef/\#ifndef directive.
|
||||
// FIXME: better to pass in a list (or tree!) of Tokens.
|
||||
void Elifndef(SourceLocation Loc, SourceRange ConditionRange,
|
||||
SourceLocation IfLoc) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#else is seen.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param IfLoc the source location of the \#if/\#ifdef/\#ifndef directive.
|
||||
void Else(SourceLocation Loc, SourceLocation IfLoc) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
|
||||
/// Hook called whenever an \#endif is seen.
|
||||
/// \param Loc the source location of the directive.
|
||||
/// \param IfLoc the source location of the \#if/\#ifdef/\#ifndef directive.
|
||||
void Endif(SourceLocation Loc, SourceLocation IfLoc) override {
|
||||
setSeenNoTrivialPPDirective();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clang
|
||||
|
||||
#endif // LLVM_CLANG_LEX_NO_TRIVIAL_PPDIRECTIVE_TRACER_H
|
||||
@ -82,6 +82,7 @@ class PreprocessorLexer;
|
||||
class PreprocessorOptions;
|
||||
class ScratchBuffer;
|
||||
class TargetInfo;
|
||||
class NoTrivialPPDirectiveTracer;
|
||||
|
||||
namespace Builtin {
|
||||
class Context;
|
||||
@ -353,6 +354,11 @@ private:
|
||||
/// First pp-token source location in current translation unit.
|
||||
SourceLocation FirstPPTokenLoc;
|
||||
|
||||
/// A preprocessor directive tracer to trace whether the preprocessing
|
||||
/// state changed. These changes would mean most semantically observable
|
||||
/// preprocessor state, particularly anything that is order dependent.
|
||||
NoTrivialPPDirectiveTracer *DirTracer = nullptr;
|
||||
|
||||
/// A position within a C++20 import-seq.
|
||||
class StdCXXImportSeq {
|
||||
public:
|
||||
@ -609,6 +615,8 @@ private:
|
||||
return State == NamedModuleImplementation && !getName().contains(':');
|
||||
}
|
||||
|
||||
bool isNotAModuleDecl() const { return State == NotAModuleDecl; }
|
||||
|
||||
StringRef getName() const {
|
||||
assert(isNamedModule() && "Can't get name from a non named module");
|
||||
return Name;
|
||||
@ -3091,6 +3099,10 @@ public:
|
||||
bool setDeserializedSafeBufferOptOutMap(
|
||||
const SmallVectorImpl<SourceLocation> &SrcLocSeqs);
|
||||
|
||||
/// Whether we've seen pp-directives which may have changed the preprocessing
|
||||
/// state.
|
||||
bool hasSeenNoTrivialPPDirective() const;
|
||||
|
||||
private:
|
||||
/// Helper functions to forward lexing to the actual lexer. They all share the
|
||||
/// same signature.
|
||||
|
||||
@ -86,12 +86,12 @@ public:
|
||||
// macro stringizing or charizing operator.
|
||||
CommaAfterElided = 0x200, // The comma following this token was elided (MS).
|
||||
IsEditorPlaceholder = 0x400, // This identifier is a placeholder.
|
||||
|
||||
IsReinjected = 0x800, // A phase 4 token that was produced before and
|
||||
// re-added, e.g. via EnterTokenStream. Annotation
|
||||
// tokens are *not* reinjected.
|
||||
FirstPPToken = 0x1000, // This token is the first pp token in the
|
||||
// translation unit.
|
||||
IsReinjected = 0x800, // A phase 4 token that was produced before and
|
||||
// re-added, e.g. via EnterTokenStream. Annotation
|
||||
// tokens are *not* reinjected.
|
||||
HasSeenNoTrivialPPDirective =
|
||||
0x1000, // Whether we've seen any 'no-trivial' pp-directives before
|
||||
// current position.
|
||||
};
|
||||
|
||||
tok::TokenKind getKind() const { return Kind; }
|
||||
@ -321,8 +321,9 @@ public:
|
||||
/// lexer uses identifier tokens to represent placeholders.
|
||||
bool isEditorPlaceholder() const { return getFlag(IsEditorPlaceholder); }
|
||||
|
||||
/// Returns true if this token is the first pp-token.
|
||||
bool isFirstPPToken() const { return getFlag(FirstPPToken); }
|
||||
bool hasSeenNoTrivialPPDirective() const {
|
||||
return getFlag(HasSeenNoTrivialPPDirective);
|
||||
}
|
||||
};
|
||||
|
||||
/// Information about the conditional stack (\#if directives)
|
||||
|
||||
@ -9837,7 +9837,7 @@ public:
|
||||
SourceLocation ModuleLoc, ModuleDeclKind MDK,
|
||||
ModuleIdPath Path, ModuleIdPath Partition,
|
||||
ModuleImportState &ImportState,
|
||||
bool IntroducerIsFirstPPToken);
|
||||
bool SeenNoTrivialPPDirective);
|
||||
|
||||
/// The parser has processed a global-module-fragment declaration that begins
|
||||
/// the definition of the global module fragment of the current module unit.
|
||||
|
||||
@ -60,16 +60,18 @@ template <class Emitter> class OptionScope final {
|
||||
public:
|
||||
/// Root constructor, compiling or discarding primitives.
|
||||
OptionScope(Compiler<Emitter> *Ctx, bool NewDiscardResult,
|
||||
bool NewInitializing)
|
||||
bool NewInitializing, bool NewToLValue)
|
||||
: Ctx(Ctx), OldDiscardResult(Ctx->DiscardResult),
|
||||
OldInitializing(Ctx->Initializing) {
|
||||
OldInitializing(Ctx->Initializing), OldToLValue(NewToLValue) {
|
||||
Ctx->DiscardResult = NewDiscardResult;
|
||||
Ctx->Initializing = NewInitializing;
|
||||
Ctx->ToLValue = NewToLValue;
|
||||
}
|
||||
|
||||
~OptionScope() {
|
||||
Ctx->DiscardResult = OldDiscardResult;
|
||||
Ctx->Initializing = OldInitializing;
|
||||
Ctx->ToLValue = OldToLValue;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -78,6 +80,7 @@ private:
|
||||
/// Old discard flag to restore.
|
||||
bool OldDiscardResult;
|
||||
bool OldInitializing;
|
||||
bool OldToLValue;
|
||||
};
|
||||
|
||||
template <class Emitter>
|
||||
@ -222,6 +225,9 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) {
|
||||
|
||||
switch (CE->getCastKind()) {
|
||||
case CK_LValueToRValue: {
|
||||
if (ToLValue && CE->getType()->isPointerType())
|
||||
return this->delegate(SubExpr);
|
||||
|
||||
if (SubExpr->getType().isVolatileQualified())
|
||||
return this->emitInvalidCast(CastKind::Volatile, /*Fatal=*/true, CE);
|
||||
|
||||
@ -2974,20 +2980,25 @@ bool Compiler<Emitter>::VisitCompoundLiteralExpr(const CompoundLiteralExpr *E) {
|
||||
if (T && !E->isLValue())
|
||||
return this->delegate(Init);
|
||||
|
||||
if (std::optional<unsigned> GlobalIndex = P.createGlobal(E)) {
|
||||
if (!this->emitGetPtrGlobal(*GlobalIndex, E))
|
||||
std::optional<unsigned> GlobalIndex = P.createGlobal(E);
|
||||
if (!GlobalIndex)
|
||||
return false;
|
||||
|
||||
if (!this->emitGetPtrGlobal(*GlobalIndex, E))
|
||||
return false;
|
||||
|
||||
// Since this is a global variable, we might've already seen,
|
||||
// don't do it again.
|
||||
if (P.isGlobalInitialized(*GlobalIndex))
|
||||
return true;
|
||||
|
||||
if (T) {
|
||||
if (!this->visit(Init))
|
||||
return false;
|
||||
|
||||
if (T) {
|
||||
if (!this->visit(Init))
|
||||
return false;
|
||||
return this->emitInitGlobal(*T, *GlobalIndex, E);
|
||||
}
|
||||
|
||||
return this->visitInitializer(Init) && this->emitFinishInit(E);
|
||||
return this->emitInitGlobal(*T, *GlobalIndex, E);
|
||||
}
|
||||
|
||||
return false;
|
||||
return this->visitInitializer(Init) && this->emitFinishInit(E);
|
||||
}
|
||||
|
||||
// Otherwise, use a local variable.
|
||||
@ -4140,13 +4151,13 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) {
|
||||
|
||||
template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) {
|
||||
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true,
|
||||
/*NewInitializing=*/false);
|
||||
/*NewInitializing=*/false, /*ToLValue=*/false);
|
||||
return this->Visit(E);
|
||||
}
|
||||
|
||||
template <class Emitter> bool Compiler<Emitter>::delegate(const Expr *E) {
|
||||
// We're basically doing:
|
||||
// OptionScope<Emitter> Scope(this, DicardResult, Initializing);
|
||||
// OptionScope<Emitter> Scope(this, DicardResult, Initializing, ToLValue);
|
||||
// but that's unnecessary of course.
|
||||
return this->Visit(E);
|
||||
}
|
||||
@ -4174,7 +4185,7 @@ template <class Emitter> bool Compiler<Emitter>::visit(const Expr *E) {
|
||||
// Otherwise,we have a primitive return value, produce the value directly
|
||||
// and push it on the stack.
|
||||
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
|
||||
/*NewInitializing=*/false);
|
||||
/*NewInitializing=*/false, /*ToLValue=*/ToLValue);
|
||||
return this->Visit(E);
|
||||
}
|
||||
|
||||
@ -4183,7 +4194,13 @@ bool Compiler<Emitter>::visitInitializer(const Expr *E) {
|
||||
assert(!canClassify(E->getType()));
|
||||
|
||||
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
|
||||
/*NewInitializing=*/true);
|
||||
/*NewInitializing=*/true, /*ToLValue=*/false);
|
||||
return this->Visit(E);
|
||||
}
|
||||
|
||||
template <class Emitter> bool Compiler<Emitter>::visitAsLValue(const Expr *E) {
|
||||
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
|
||||
/*NewInitializing=*/false, /*ToLValue=*/true);
|
||||
return this->Visit(E);
|
||||
}
|
||||
|
||||
@ -4944,7 +4961,6 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val,
|
||||
template <class Emitter>
|
||||
bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
|
||||
unsigned BuiltinID) {
|
||||
|
||||
if (BuiltinID == Builtin::BI__builtin_constant_p) {
|
||||
// Void argument is always invalid and harder to handle later.
|
||||
if (E->getArg(0)->getType()->isVoidType()) {
|
||||
@ -4989,11 +5005,31 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Context::isUnevaluatedBuiltin(BuiltinID)) {
|
||||
// Put arguments on the stack.
|
||||
for (const auto *Arg : E->arguments()) {
|
||||
if (!this->visit(Arg))
|
||||
// Prepare function arguments including special cases.
|
||||
switch (BuiltinID) {
|
||||
case Builtin::BI__builtin_object_size:
|
||||
case Builtin::BI__builtin_dynamic_object_size: {
|
||||
assert(E->getNumArgs() == 2);
|
||||
const Expr *Arg0 = E->getArg(0);
|
||||
if (Arg0->isGLValue()) {
|
||||
if (!this->visit(Arg0))
|
||||
return false;
|
||||
|
||||
} else {
|
||||
if (!this->visitAsLValue(Arg0))
|
||||
return false;
|
||||
}
|
||||
if (!this->visit(E->getArg(1)))
|
||||
return false;
|
||||
|
||||
} break;
|
||||
default:
|
||||
if (!Context::isUnevaluatedBuiltin(BuiltinID)) {
|
||||
// Put arguments on the stack.
|
||||
for (const auto *Arg : E->arguments()) {
|
||||
if (!this->visit(Arg))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5146,7 +5182,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
|
||||
if (!this->emitCheckPseudoDtor(E))
|
||||
return false;
|
||||
const Expr *Base = PD->getBase();
|
||||
if (!Base->isGLValue())
|
||||
// E.g. `using T = int; 0.~T();`.
|
||||
if (OptPrimType BaseT = classify(Base); !BaseT || BaseT != PT_Ptr)
|
||||
return this->discard(Base);
|
||||
if (!this->visit(Base))
|
||||
return false;
|
||||
@ -6745,6 +6782,22 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
|
||||
// value.
|
||||
bool IsReference = D->getType()->isReferenceType();
|
||||
|
||||
// Function parameters.
|
||||
// Note that it's important to check them first since we might have a local
|
||||
// variable created for a ParmVarDecl as well.
|
||||
if (const auto *PVD = dyn_cast<ParmVarDecl>(D)) {
|
||||
if (Ctx.getLangOpts().CPlusPlus && !Ctx.getLangOpts().CPlusPlus11 &&
|
||||
!D->getType()->isIntegralOrEnumerationType()) {
|
||||
return this->emitInvalidDeclRef(cast<DeclRefExpr>(E),
|
||||
/*InitializerFailed=*/false, E);
|
||||
}
|
||||
if (auto It = this->Params.find(PVD); It != this->Params.end()) {
|
||||
if (IsReference || !It->second.IsPtr)
|
||||
return this->emitGetParam(classifyPrim(E), It->second.Offset, E);
|
||||
|
||||
return this->emitGetPtrParam(It->second.Offset, E);
|
||||
}
|
||||
}
|
||||
// Local variables.
|
||||
if (auto It = Locals.find(D); It != Locals.end()) {
|
||||
const unsigned Offset = It->second.Offset;
|
||||
@ -6762,20 +6815,6 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
|
||||
|
||||
return this->emitGetPtrGlobal(*GlobalIndex, E);
|
||||
}
|
||||
// Function parameters.
|
||||
if (const auto *PVD = dyn_cast<ParmVarDecl>(D)) {
|
||||
if (Ctx.getLangOpts().CPlusPlus && !Ctx.getLangOpts().CPlusPlus11 &&
|
||||
!D->getType()->isIntegralOrEnumerationType()) {
|
||||
return this->emitInvalidDeclRef(cast<DeclRefExpr>(E),
|
||||
/*InitializerFailed=*/false, E);
|
||||
}
|
||||
if (auto It = this->Params.find(PVD); It != this->Params.end()) {
|
||||
if (IsReference || !It->second.IsPtr)
|
||||
return this->emitGetParam(classifyPrim(E), It->second.Offset, E);
|
||||
|
||||
return this->emitGetPtrParam(It->second.Offset, E);
|
||||
}
|
||||
}
|
||||
|
||||
// In case we need to re-visit a declaration.
|
||||
auto revisit = [&](const VarDecl *VD) -> bool {
|
||||
|
||||
@ -282,6 +282,7 @@ protected:
|
||||
/// been created. visitInitializer() then relies on a pointer to this
|
||||
/// variable being on top of the stack.
|
||||
bool visitInitializer(const Expr *E);
|
||||
bool visitAsLValue(const Expr *E);
|
||||
/// Evaluates an expression for side effects and discards the result.
|
||||
bool discard(const Expr *E);
|
||||
/// Just pass evaluation on to \p E. This leaves all the parsing flags
|
||||
@ -426,6 +427,7 @@ protected:
|
||||
bool DiscardResult = false;
|
||||
|
||||
bool InStmtExpr = false;
|
||||
bool ToLValue = false;
|
||||
|
||||
/// Flag inidicating if we're initializing an already created
|
||||
/// variable. This is set in visitInitializer().
|
||||
|
||||
@ -398,17 +398,11 @@ const llvm::fltSemantics &Context::getFloatSemantics(QualType T) const {
|
||||
}
|
||||
|
||||
bool Context::Run(State &Parent, const Function *Func) {
|
||||
|
||||
{
|
||||
InterpState State(Parent, *P, Stk, *this, Func);
|
||||
if (Interpret(State)) {
|
||||
assert(Stk.empty());
|
||||
return true;
|
||||
}
|
||||
// State gets destroyed here, so the Stk.clear() below doesn't accidentally
|
||||
// remove values the State's destructor might access.
|
||||
InterpState State(Parent, *P, Stk, *this, Func);
|
||||
if (Interpret(State)) {
|
||||
assert(Stk.empty());
|
||||
return true;
|
||||
}
|
||||
|
||||
Stk.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ namespace interp {
|
||||
class Function;
|
||||
class Program;
|
||||
class State;
|
||||
enum PrimType : unsigned;
|
||||
enum PrimType : uint8_t;
|
||||
|
||||
struct ParamOffset {
|
||||
unsigned Offset;
|
||||
|
||||
@ -24,7 +24,7 @@ class Record;
|
||||
class SourceInfo;
|
||||
struct InitMap;
|
||||
struct Descriptor;
|
||||
enum PrimType : unsigned;
|
||||
enum PrimType : uint8_t;
|
||||
|
||||
using DeclTy = llvm::PointerUnion<const Decl *, const Expr *>;
|
||||
using InitMapPtr = std::optional<std::pair<bool, std::shared_ptr<InitMap>>>;
|
||||
|
||||
@ -28,7 +28,7 @@ namespace interp {
|
||||
class Program;
|
||||
class ByteCodeEmitter;
|
||||
class Pointer;
|
||||
enum PrimType : uint32_t;
|
||||
enum PrimType : uint8_t;
|
||||
|
||||
/// Describes a scope block.
|
||||
///
|
||||
|
||||
@ -1852,6 +1852,11 @@ bool EndLifetime(InterpState &S, CodePtr OpPC) {
|
||||
const auto &Ptr = S.Stk.peek<Pointer>();
|
||||
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
|
||||
return false;
|
||||
|
||||
// FIXME: We need per-element lifetime information for primitive arrays.
|
||||
if (Ptr.isArrayElement())
|
||||
return true;
|
||||
|
||||
endLifetimeRecurse(Ptr.narrow());
|
||||
return true;
|
||||
}
|
||||
@ -1861,6 +1866,11 @@ bool EndLifetimePop(InterpState &S, CodePtr OpPC) {
|
||||
const auto &Ptr = S.Stk.pop<Pointer>();
|
||||
if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy))
|
||||
return false;
|
||||
|
||||
// FIXME: We need per-element lifetime information for primitive arrays.
|
||||
if (Ptr.isArrayElement())
|
||||
return true;
|
||||
|
||||
endLifetimeRecurse(Ptr.narrow());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -18,10 +18,6 @@ using namespace clang::interp;
|
||||
|
||||
void Block::addPointer(Pointer *P) {
|
||||
assert(P);
|
||||
if (IsStatic) {
|
||||
assert(!Pointers);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert(!hasPointer(P));
|
||||
@ -39,10 +35,6 @@ void Block::addPointer(Pointer *P) {
|
||||
void Block::removePointer(Pointer *P) {
|
||||
assert(P->isBlockPointer());
|
||||
assert(P);
|
||||
if (IsStatic) {
|
||||
assert(!Pointers);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert(hasPointer(P));
|
||||
@ -74,10 +66,6 @@ void Block::replacePointer(Pointer *Old, Pointer *New) {
|
||||
assert(New);
|
||||
assert(New->isBlockPointer());
|
||||
assert(Old != New);
|
||||
if (IsStatic) {
|
||||
assert(!Pointers);
|
||||
return;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
assert(hasPointer(Old));
|
||||
#endif
|
||||
|
||||
@ -22,7 +22,7 @@ class Block;
|
||||
class DeadBlock;
|
||||
class InterpState;
|
||||
class Pointer;
|
||||
enum PrimType : unsigned;
|
||||
enum PrimType : uint8_t;
|
||||
|
||||
/// A memory block, either on the stack or in the heap.
|
||||
///
|
||||
@ -50,9 +50,9 @@ private:
|
||||
|
||||
public:
|
||||
/// Creates a new block.
|
||||
Block(unsigned EvalID, const std::optional<unsigned> &DeclID,
|
||||
const Descriptor *Desc, bool IsStatic = false, bool IsExtern = false,
|
||||
bool IsWeak = false, bool IsDummy = false)
|
||||
Block(unsigned EvalID, UnsignedOrNone DeclID, const Descriptor *Desc,
|
||||
bool IsStatic = false, bool IsExtern = false, bool IsWeak = false,
|
||||
bool IsDummy = false)
|
||||
: Desc(Desc), DeclID(DeclID), EvalID(EvalID), IsStatic(IsStatic) {
|
||||
assert(Desc);
|
||||
AccessFlags |= (ExternFlag * IsExtern);
|
||||
@ -62,8 +62,7 @@ public:
|
||||
|
||||
Block(unsigned EvalID, const Descriptor *Desc, bool IsStatic = false,
|
||||
bool IsExtern = false, bool IsWeak = false, bool IsDummy = false)
|
||||
: Desc(Desc), DeclID((unsigned)-1), EvalID(EvalID), IsStatic(IsStatic),
|
||||
IsDynamic(false) {
|
||||
: Desc(Desc), EvalID(EvalID), IsStatic(IsStatic), IsDynamic(false) {
|
||||
assert(Desc);
|
||||
AccessFlags |= (ExternFlag * IsExtern);
|
||||
AccessFlags |= (WeakFlag * IsWeak);
|
||||
@ -87,7 +86,7 @@ public:
|
||||
/// Returns the size of the block.
|
||||
unsigned getSize() const { return Desc->getAllocSize(); }
|
||||
/// Returns the declaration ID.
|
||||
std::optional<unsigned> getDeclID() const { return DeclID; }
|
||||
UnsignedOrNone getDeclID() const { return DeclID; }
|
||||
/// Returns whether the data of this block has been initialized via
|
||||
/// invoking the Ctor func.
|
||||
bool isInitialized() const { return IsInitialized; }
|
||||
@ -177,7 +176,7 @@ private:
|
||||
/// Start of the chain of pointers.
|
||||
Pointer *Pointers = nullptr;
|
||||
/// Unique identifier of the declaration.
|
||||
std::optional<unsigned> DeclID;
|
||||
UnsignedOrNone DeclID = std::nullopt;
|
||||
const unsigned EvalID = ~0u;
|
||||
/// Flag indicating if the block has static storage duration.
|
||||
bool IsStatic = false;
|
||||
|
||||
@ -2170,29 +2170,32 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned computeFullDescSize(const ASTContext &ASTCtx,
|
||||
const Descriptor *Desc) {
|
||||
|
||||
static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
|
||||
const Descriptor *Desc) {
|
||||
if (Desc->isPrimitive())
|
||||
return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();
|
||||
|
||||
if (Desc->isArray())
|
||||
return ASTCtx.getTypeSizeInChars(Desc->getElemQualType()).getQuantity() *
|
||||
Desc->getNumElems();
|
||||
if (Desc->isRecord()) {
|
||||
// Can't use Descriptor::getType() as that may return a pointer type. Look
|
||||
// at the decl directly.
|
||||
return ASTCtx
|
||||
.getTypeSizeInChars(
|
||||
ASTCtx.getCanonicalTagType(Desc->ElemRecord->getDecl()))
|
||||
.getQuantity();
|
||||
}
|
||||
|
||||
if (Desc->isRecord())
|
||||
return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();
|
||||
|
||||
llvm_unreachable("Unhandled descriptor type");
|
||||
return 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/// Compute the byte offset of \p Ptr in the full declaration.
|
||||
static unsigned computePointerOffset(const ASTContext &ASTCtx,
|
||||
const Pointer &Ptr) {
|
||||
unsigned Result = 0;
|
||||
|
||||
Pointer P = Ptr;
|
||||
while (P.isArrayElement() || P.isField()) {
|
||||
while (P.isField() || P.isArrayElement()) {
|
||||
P = P.expand();
|
||||
const Descriptor *D = P.getFieldDesc();
|
||||
|
||||
@ -2205,7 +2208,6 @@ static unsigned computePointerOffset(const ASTContext &ASTCtx,
|
||||
Result += ElemSize * P.getIndex();
|
||||
P = P.expand().getArray();
|
||||
} else if (P.isBaseClass()) {
|
||||
|
||||
const auto *RD = cast<CXXRecordDecl>(D->asDecl());
|
||||
bool IsVirtual = Ptr.isVirtualBaseClass();
|
||||
P = P.getBase();
|
||||
@ -2234,30 +2236,136 @@ static unsigned computePointerOffset(const ASTContext &ASTCtx,
|
||||
return Result;
|
||||
}
|
||||
|
||||
/// Does Ptr point to the last subobject?
|
||||
static bool pointsToLastObject(const Pointer &Ptr) {
|
||||
Pointer P = Ptr;
|
||||
while (!P.isRoot()) {
|
||||
|
||||
if (P.isArrayElement()) {
|
||||
P = P.expand().getArray();
|
||||
continue;
|
||||
}
|
||||
if (P.isBaseClass()) {
|
||||
if (P.getRecord()->getNumFields() > 0)
|
||||
return false;
|
||||
P = P.getBase();
|
||||
continue;
|
||||
}
|
||||
|
||||
Pointer Base = P.getBase();
|
||||
if (const Record *R = Base.getRecord()) {
|
||||
assert(P.getField());
|
||||
if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
|
||||
return false;
|
||||
}
|
||||
P = Base;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Does Ptr point to the last object AND to a flexible array member?
|
||||
static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr) {
|
||||
auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
|
||||
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
|
||||
FAMKind StrictFlexArraysLevel =
|
||||
Ctx.getLangOpts().getStrictFlexArraysLevel();
|
||||
|
||||
if (StrictFlexArraysLevel == FAMKind::Default)
|
||||
return true;
|
||||
|
||||
unsigned NumElems = FieldDesc->getNumElems();
|
||||
if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
|
||||
return true;
|
||||
|
||||
if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const Descriptor *FieldDesc = Ptr.getFieldDesc();
|
||||
if (!FieldDesc->isArray())
|
||||
return false;
|
||||
|
||||
return Ptr.isDummy() && pointsToLastObject(Ptr) &&
|
||||
isFlexibleArrayMember(FieldDesc);
|
||||
}
|
||||
|
||||
static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
|
||||
const InterpFrame *Frame,
|
||||
const CallExpr *Call) {
|
||||
const ASTContext &ASTCtx = S.getASTContext();
|
||||
PrimType KindT = *S.getContext().classify(Call->getArg(1));
|
||||
[[maybe_unused]] unsigned Kind = popToAPSInt(S.Stk, KindT).getZExtValue();
|
||||
|
||||
// From the GCC docs:
|
||||
// Kind is an integer constant from 0 to 3. If the least significant bit is
|
||||
// clear, objects are whole variables. If it is set, a closest surrounding
|
||||
// subobject is considered the object a pointer points to. The second bit
|
||||
// determines if maximum or minimum of remaining bytes is computed.
|
||||
unsigned Kind = popToAPSInt(S.Stk, KindT).getZExtValue();
|
||||
assert(Kind <= 3 && "unexpected kind");
|
||||
|
||||
bool UseFieldDesc = (Kind & 1u);
|
||||
bool ReportMinimum = (Kind & 2u);
|
||||
const Pointer &Ptr = S.Stk.pop<Pointer>();
|
||||
|
||||
if (Ptr.isZero())
|
||||
if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
|
||||
// "If there are any side effects in them, it returns (size_t) -1
|
||||
// for type 0 or 1 and (size_t) 0 for type 2 or 3."
|
||||
pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Ptr.isZero() || !Ptr.isBlockPointer())
|
||||
return false;
|
||||
|
||||
// We can't load through pointers.
|
||||
if (Ptr.isDummy() && Ptr.getType()->isPointerType())
|
||||
return false;
|
||||
|
||||
bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
|
||||
const Descriptor *DeclDesc = Ptr.getDeclDesc();
|
||||
if (!DeclDesc)
|
||||
assert(DeclDesc);
|
||||
|
||||
if (!UseFieldDesc || DetermineForCompleteObject) {
|
||||
// Lower bound, so we can't fall back to this.
|
||||
if (ReportMinimum && !DetermineForCompleteObject)
|
||||
return false;
|
||||
|
||||
// Can't read beyond the pointer decl desc.
|
||||
if (!UseFieldDesc && !ReportMinimum && DeclDesc->getType()->isPointerType())
|
||||
return false;
|
||||
} else {
|
||||
if (isUserWritingOffTheEnd(ASTCtx, Ptr.expand())) {
|
||||
// If we cannot determine the size of the initial allocation, then we
|
||||
// can't given an accurate upper-bound. However, we are still able to give
|
||||
// conservative lower-bounds for Type=3.
|
||||
if (Kind == 1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
|
||||
assert(Desc);
|
||||
|
||||
std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
|
||||
if (!FullSize)
|
||||
return false;
|
||||
|
||||
const ASTContext &ASTCtx = S.getASTContext();
|
||||
unsigned ByteOffset;
|
||||
if (UseFieldDesc) {
|
||||
if (Ptr.isBaseClass())
|
||||
ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
|
||||
computePointerOffset(ASTCtx, Ptr);
|
||||
else
|
||||
ByteOffset =
|
||||
computePointerOffset(ASTCtx, Ptr) -
|
||||
computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
|
||||
} else
|
||||
ByteOffset = computePointerOffset(ASTCtx, Ptr);
|
||||
|
||||
unsigned ByteOffset = computePointerOffset(ASTCtx, Ptr);
|
||||
unsigned FullSize = computeFullDescSize(ASTCtx, DeclDesc);
|
||||
|
||||
pushInteger(S, FullSize - ByteOffset, Call->getType());
|
||||
assert(ByteOffset <= *FullSize);
|
||||
unsigned Result = *FullSize - ByteOffset;
|
||||
|
||||
pushInteger(S, Result, Call->getType());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -26,33 +26,33 @@ InterpStack::~InterpStack() {
|
||||
std::free(Chunk);
|
||||
Chunk = nullptr;
|
||||
StackSize = 0;
|
||||
#ifndef NDEBUG
|
||||
ItemTypes.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
// We keep the last chunk around to reuse.
|
||||
void InterpStack::clear() {
|
||||
if (!Chunk)
|
||||
return;
|
||||
|
||||
if (Chunk->Next)
|
||||
std::free(Chunk->Next);
|
||||
|
||||
assert(Chunk);
|
||||
StackSize = 0;
|
||||
#ifndef NDEBUG
|
||||
ItemTypes.clear();
|
||||
#endif
|
||||
for (PrimType Item : llvm::reverse(ItemTypes)) {
|
||||
TYPE_SWITCH(Item, { this->discard<T>(); });
|
||||
}
|
||||
assert(ItemTypes.empty());
|
||||
assert(empty());
|
||||
}
|
||||
|
||||
void InterpStack::clearTo(size_t NewSize) {
|
||||
assert(NewSize <= size());
|
||||
size_t ToShrink = size() - NewSize;
|
||||
if (ToShrink == 0)
|
||||
if (NewSize == 0)
|
||||
return clear();
|
||||
if (NewSize == size())
|
||||
return;
|
||||
|
||||
shrink(ToShrink);
|
||||
assert(NewSize <= size());
|
||||
for (PrimType Item : llvm::reverse(ItemTypes)) {
|
||||
TYPE_SWITCH(Item, { this->discard<T>(); });
|
||||
|
||||
if (size() == NewSize)
|
||||
break;
|
||||
}
|
||||
|
||||
// Note: discard() above already removed the types from ItemTypes.
|
||||
assert(size() == NewSize);
|
||||
}
|
||||
|
||||
@ -105,25 +105,9 @@ void InterpStack::shrink(size_t Size) {
|
||||
|
||||
Chunk->End -= Size;
|
||||
StackSize -= Size;
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t TypesSize = 0;
|
||||
for (PrimType T : ItemTypes)
|
||||
TYPE_SWITCH(T, { TypesSize += aligned_size<T>(); });
|
||||
|
||||
size_t StackSize = size();
|
||||
while (TypesSize > StackSize) {
|
||||
TYPE_SWITCH(ItemTypes.back(), {
|
||||
TypesSize -= aligned_size<T>();
|
||||
ItemTypes.pop_back();
|
||||
});
|
||||
}
|
||||
assert(TypesSize == StackSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InterpStack::dump() const {
|
||||
#ifndef NDEBUG
|
||||
llvm::errs() << "Items: " << ItemTypes.size() << ". Size: " << size() << '\n';
|
||||
if (ItemTypes.empty())
|
||||
return;
|
||||
@ -133,11 +117,11 @@ void InterpStack::dump() const {
|
||||
|
||||
// The type of the item on the top of the stack is inserted to the back
|
||||
// of the vector, so the iteration has to happen backwards.
|
||||
for (auto TyIt = ItemTypes.rbegin(); TyIt != ItemTypes.rend(); ++TyIt) {
|
||||
Offset += align(primSize(*TyIt));
|
||||
for (PrimType Item : llvm::reverse(ItemTypes)) {
|
||||
Offset += align(primSize(Item));
|
||||
|
||||
llvm::errs() << Index << '/' << Offset << ": ";
|
||||
TYPE_SWITCH(*TyIt, {
|
||||
TYPE_SWITCH(Item, {
|
||||
const T &V = peek<T>(Offset);
|
||||
llvm::errs() << V;
|
||||
});
|
||||
@ -145,5 +129,4 @@ void InterpStack::dump() const {
|
||||
|
||||
++Index;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
#include "IntegralAP.h"
|
||||
#include "MemberPointer.h"
|
||||
#include "PrimType.h"
|
||||
#include <vector>
|
||||
|
||||
namespace clang {
|
||||
namespace interp {
|
||||
@ -33,18 +32,14 @@ public:
|
||||
/// Constructs a value in place on the top of the stack.
|
||||
template <typename T, typename... Tys> void push(Tys &&...Args) {
|
||||
new (grow(aligned_size<T>())) T(std::forward<Tys>(Args)...);
|
||||
#ifndef NDEBUG
|
||||
ItemTypes.push_back(toPrimType<T>());
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Returns the value from the top of the stack and removes it.
|
||||
template <typename T> T pop() {
|
||||
#ifndef NDEBUG
|
||||
assert(!ItemTypes.empty());
|
||||
assert(ItemTypes.back() == toPrimType<T>());
|
||||
ItemTypes.pop_back();
|
||||
#endif
|
||||
T *Ptr = &peekInternal<T>();
|
||||
T Value = std::move(*Ptr);
|
||||
shrink(aligned_size<T>());
|
||||
@ -53,22 +48,20 @@ public:
|
||||
|
||||
/// Discards the top value from the stack.
|
||||
template <typename T> void discard() {
|
||||
#ifndef NDEBUG
|
||||
assert(!ItemTypes.empty());
|
||||
assert(ItemTypes.back() == toPrimType<T>());
|
||||
ItemTypes.pop_back();
|
||||
#endif
|
||||
T *Ptr = &peekInternal<T>();
|
||||
Ptr->~T();
|
||||
if constexpr (!std::is_trivially_destructible_v<T>) {
|
||||
Ptr->~T();
|
||||
}
|
||||
shrink(aligned_size<T>());
|
||||
}
|
||||
|
||||
/// Returns a reference to the value on the top of the stack.
|
||||
template <typename T> T &peek() const {
|
||||
#ifndef NDEBUG
|
||||
assert(!ItemTypes.empty());
|
||||
assert(ItemTypes.back() == toPrimType<T>());
|
||||
#endif
|
||||
return peekInternal<T>();
|
||||
}
|
||||
|
||||
@ -83,7 +76,7 @@ public:
|
||||
/// Returns the size of the stack in bytes.
|
||||
size_t size() const { return StackSize; }
|
||||
|
||||
/// Clears the stack without calling any destructors.
|
||||
/// Clears the stack.
|
||||
void clear();
|
||||
void clearTo(size_t NewSize);
|
||||
|
||||
@ -146,9 +139,11 @@ private:
|
||||
/// Total size of the stack.
|
||||
size_t StackSize = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
/// vector recording the type of data we pushed into the stack.
|
||||
std::vector<PrimType> ItemTypes;
|
||||
/// SmallVector recording the type of data we pushed into the stack.
|
||||
/// We don't usually need this during normal code interpretation but
|
||||
/// when aborting, we need type information to call the destructors
|
||||
/// for what's left on the stack.
|
||||
llvm::SmallVector<PrimType> ItemTypes;
|
||||
|
||||
template <typename T> static constexpr PrimType toPrimType() {
|
||||
if constexpr (std::is_same_v<T, Pointer>)
|
||||
@ -192,7 +187,6 @@ private:
|
||||
|
||||
llvm_unreachable("unknown type push()'ed into InterpStack");
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace interp
|
||||
|
||||
@ -45,6 +45,12 @@ InterpState::~InterpState() {
|
||||
|
||||
while (DeadBlocks) {
|
||||
DeadBlock *Next = DeadBlocks->Next;
|
||||
|
||||
// There might be a pointer in a global structure pointing to the dead
|
||||
// block.
|
||||
for (Pointer *P = DeadBlocks->B.Pointers; P; P = P->asBlockPointer().Next)
|
||||
DeadBlocks->B.removePointer(P);
|
||||
|
||||
std::free(DeadBlocks);
|
||||
DeadBlocks = Next;
|
||||
}
|
||||
@ -53,12 +59,6 @@ InterpState::~InterpState() {
|
||||
void InterpState::cleanup() {
|
||||
// As a last resort, make sure all pointers still pointing to a dead block
|
||||
// don't point to it anymore.
|
||||
for (DeadBlock *DB = DeadBlocks; DB; DB = DB->Next) {
|
||||
for (Pointer *P = DB->B.Pointers; P; P = P->asBlockPointer().Next) {
|
||||
P->PointeeStorage.BS.Pointee = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Alloc.cleanup();
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ class DeadBlock;
|
||||
class Pointer;
|
||||
class Context;
|
||||
template <unsigned A, bool B> class Integral;
|
||||
enum PrimType : unsigned;
|
||||
enum PrimType : uint8_t;
|
||||
|
||||
class Pointer;
|
||||
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P);
|
||||
@ -593,7 +593,7 @@ public:
|
||||
}
|
||||
|
||||
/// Returns the declaration ID.
|
||||
std::optional<unsigned> getDeclID() const {
|
||||
UnsignedOrNone getDeclID() const {
|
||||
if (isBlockPointer()) {
|
||||
assert(asBlockPointer().Pointee);
|
||||
return asBlockPointer().Pointee->getDeclID();
|
||||
|
||||
@ -31,7 +31,7 @@ template <bool Signed> class IntegralAP;
|
||||
template <unsigned Bits, bool Signed> class Integral;
|
||||
|
||||
/// Enumeration of the primitive types of the VM.
|
||||
enum PrimType : unsigned {
|
||||
enum PrimType : uint8_t {
|
||||
PT_Sint8 = 0,
|
||||
PT_Uint8 = 1,
|
||||
PT_Sint16 = 2,
|
||||
@ -51,14 +51,15 @@ enum PrimType : unsigned {
|
||||
|
||||
// Like std::optional<PrimType>, but only sizeof(PrimType).
|
||||
class OptPrimType final {
|
||||
unsigned V = ~0u;
|
||||
static constexpr uint8_t None = 0xFF;
|
||||
uint8_t V = None;
|
||||
|
||||
public:
|
||||
OptPrimType() = default;
|
||||
OptPrimType(std::nullopt_t) {}
|
||||
OptPrimType(PrimType T) : V(static_cast<unsigned>(T)) {}
|
||||
|
||||
explicit constexpr operator bool() const { return V != ~0u; }
|
||||
explicit constexpr operator bool() const { return V != None; }
|
||||
PrimType operator*() const {
|
||||
assert(operator bool());
|
||||
return static_cast<PrimType>(V);
|
||||
|
||||
@ -164,8 +164,8 @@ unsigned Program::getOrCreateDummy(const DeclTy &D) {
|
||||
const auto *VD = cast<ValueDecl>(cast<const Decl *>(D));
|
||||
IsWeak = VD->isWeak();
|
||||
QT = VD->getType();
|
||||
if (const auto *RT = QT->getAs<ReferenceType>())
|
||||
QT = RT->getPointeeType();
|
||||
if (QT->isPointerOrReferenceType())
|
||||
QT = QT->getPointeeType();
|
||||
}
|
||||
assert(!QT.isNull());
|
||||
|
||||
|
||||
@ -73,6 +73,10 @@ public:
|
||||
return Globals[Idx]->block();
|
||||
}
|
||||
|
||||
bool isGlobalInitialized(unsigned Index) const {
|
||||
return getPtrGlobal(Index).isInitialized();
|
||||
}
|
||||
|
||||
/// Finds a global's index.
|
||||
std::optional<unsigned> getGlobal(const ValueDecl *VD);
|
||||
std::optional<unsigned> getGlobal(const Expr *E);
|
||||
@ -152,7 +156,7 @@ public:
|
||||
};
|
||||
|
||||
/// Returns the current declaration ID.
|
||||
std::optional<unsigned> getCurrentDecl() const {
|
||||
UnsignedOrNone getCurrentDecl() const {
|
||||
if (CurrentDeclaration == NoDeclaration)
|
||||
return std::nullopt;
|
||||
return CurrentDeclaration;
|
||||
|
||||
@ -2805,32 +2805,20 @@ bool Expr::isUnusedResultAWarning(const Expr *&WarnE, SourceLocation &Loc,
|
||||
|
||||
case CXXTemporaryObjectExprClass:
|
||||
case CXXConstructExprClass: {
|
||||
if (const CXXRecordDecl *Type = getType()->getAsCXXRecordDecl()) {
|
||||
const auto *WarnURAttr = Type->getAttr<WarnUnusedResultAttr>();
|
||||
if (Type->hasAttr<WarnUnusedAttr>() ||
|
||||
(WarnURAttr && WarnURAttr->IsCXX11NoDiscard())) {
|
||||
WarnE = this;
|
||||
Loc = getBeginLoc();
|
||||
R1 = getSourceRange();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto *CE = cast<CXXConstructExpr>(this);
|
||||
if (const CXXConstructorDecl *Ctor = CE->getConstructor()) {
|
||||
const auto *WarnURAttr = Ctor->getAttr<WarnUnusedResultAttr>();
|
||||
if (WarnURAttr && WarnURAttr->IsCXX11NoDiscard()) {
|
||||
WarnE = this;
|
||||
Loc = getBeginLoc();
|
||||
R1 = getSourceRange();
|
||||
const CXXRecordDecl *Type = getType()->getAsCXXRecordDecl();
|
||||
|
||||
if (unsigned NumArgs = CE->getNumArgs())
|
||||
R2 = SourceRange(CE->getArg(0)->getBeginLoc(),
|
||||
CE->getArg(NumArgs - 1)->getEndLoc());
|
||||
return true;
|
||||
}
|
||||
if ((Type && Type->hasAttr<WarnUnusedAttr>()) ||
|
||||
CE->hasUnusedResultAttr(Ctx)) {
|
||||
WarnE = this;
|
||||
Loc = getBeginLoc();
|
||||
R1 = getSourceRange();
|
||||
|
||||
if (unsigned NumArgs = CE->getNumArgs())
|
||||
R2 = SourceRange(CE->getArg(0)->getBeginLoc(),
|
||||
CE->getArg(NumArgs - 1)->getEndLoc());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -11711,6 +11711,43 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
|
||||
|
||||
return Success(APValue(ResultElements.data(), ResultElements.size()), E);
|
||||
}
|
||||
case clang::X86::BI__builtin_ia32_pmuldq128:
|
||||
case clang::X86::BI__builtin_ia32_pmuldq256:
|
||||
case clang::X86::BI__builtin_ia32_pmuldq512:
|
||||
case clang::X86::BI__builtin_ia32_pmuludq128:
|
||||
case clang::X86::BI__builtin_ia32_pmuludq256:
|
||||
case clang::X86::BI__builtin_ia32_pmuludq512: {
|
||||
APValue SourceLHS, SourceRHS;
|
||||
if (!EvaluateAsRValue(Info, E->getArg(0), SourceLHS) ||
|
||||
!EvaluateAsRValue(Info, E->getArg(1), SourceRHS))
|
||||
return false;
|
||||
|
||||
unsigned SourceLen = SourceLHS.getVectorLength();
|
||||
SmallVector<APValue, 4> ResultElements;
|
||||
ResultElements.reserve(SourceLen / 2);
|
||||
|
||||
for (unsigned EltNum = 0; EltNum < SourceLen; EltNum += 2) {
|
||||
APSInt LHS = SourceLHS.getVectorElt(EltNum).getInt();
|
||||
APSInt RHS = SourceRHS.getVectorElt(EltNum).getInt();
|
||||
|
||||
switch (E->getBuiltinCallee()) {
|
||||
case clang::X86::BI__builtin_ia32_pmuludq128:
|
||||
case clang::X86::BI__builtin_ia32_pmuludq256:
|
||||
case clang::X86::BI__builtin_ia32_pmuludq512:
|
||||
ResultElements.push_back(
|
||||
APValue(APSInt(llvm::APIntOps::muluExtended(LHS, RHS), true)));
|
||||
break;
|
||||
case clang::X86::BI__builtin_ia32_pmuldq128:
|
||||
case clang::X86::BI__builtin_ia32_pmuldq256:
|
||||
case clang::X86::BI__builtin_ia32_pmuldq512:
|
||||
ResultElements.push_back(
|
||||
APValue(APSInt(llvm::APIntOps::mulsExtended(LHS, RHS), false)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Success(APValue(ResultElements.data(), ResultElements.size()), E);
|
||||
}
|
||||
case Builtin::BI__builtin_elementwise_max:
|
||||
case Builtin::BI__builtin_elementwise_min: {
|
||||
APValue SourceLHS, SourceRHS;
|
||||
@ -11746,6 +11783,50 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
|
||||
|
||||
return Success(APValue(ResultElements.data(), ResultElements.size()), E);
|
||||
}
|
||||
case X86::BI__builtin_ia32_selectb_128:
|
||||
case X86::BI__builtin_ia32_selectb_256:
|
||||
case X86::BI__builtin_ia32_selectb_512:
|
||||
case X86::BI__builtin_ia32_selectw_128:
|
||||
case X86::BI__builtin_ia32_selectw_256:
|
||||
case X86::BI__builtin_ia32_selectw_512:
|
||||
case X86::BI__builtin_ia32_selectd_128:
|
||||
case X86::BI__builtin_ia32_selectd_256:
|
||||
case X86::BI__builtin_ia32_selectd_512:
|
||||
case X86::BI__builtin_ia32_selectq_128:
|
||||
case X86::BI__builtin_ia32_selectq_256:
|
||||
case X86::BI__builtin_ia32_selectq_512:
|
||||
case X86::BI__builtin_ia32_selectph_128:
|
||||
case X86::BI__builtin_ia32_selectph_256:
|
||||
case X86::BI__builtin_ia32_selectph_512:
|
||||
case X86::BI__builtin_ia32_selectpbf_128:
|
||||
case X86::BI__builtin_ia32_selectpbf_256:
|
||||
case X86::BI__builtin_ia32_selectpbf_512:
|
||||
case X86::BI__builtin_ia32_selectps_128:
|
||||
case X86::BI__builtin_ia32_selectps_256:
|
||||
case X86::BI__builtin_ia32_selectps_512:
|
||||
case X86::BI__builtin_ia32_selectpd_128:
|
||||
case X86::BI__builtin_ia32_selectpd_256:
|
||||
case X86::BI__builtin_ia32_selectpd_512: {
|
||||
// AVX512 predicated move: "Result = Mask[] ? LHS[] : RHS[]".
|
||||
APValue SourceMask, SourceLHS, SourceRHS;
|
||||
if (!EvaluateAsRValue(Info, E->getArg(0), SourceMask) ||
|
||||
!EvaluateAsRValue(Info, E->getArg(1), SourceLHS) ||
|
||||
!EvaluateAsRValue(Info, E->getArg(2), SourceRHS))
|
||||
return false;
|
||||
|
||||
APSInt Mask = SourceMask.getInt();
|
||||
unsigned SourceLen = SourceLHS.getVectorLength();
|
||||
SmallVector<APValue, 4> ResultElements;
|
||||
ResultElements.reserve(SourceLen);
|
||||
|
||||
for (unsigned EltNum = 0; EltNum < SourceLen; ++EltNum) {
|
||||
const APValue &LHS = SourceLHS.getVectorElt(EltNum);
|
||||
const APValue &RHS = SourceRHS.getVectorElt(EltNum);
|
||||
ResultElements.push_back(Mask[EltNum] ? LHS : RHS);
|
||||
}
|
||||
|
||||
return Success(APValue(ResultElements.data(), ResultElements.size()), E);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -440,37 +440,37 @@ public:
|
||||
#define GEN_CLANG_CLAUSE_CLASS
|
||||
#define CLAUSE_CLASS(Enum, Str, Class) void Visit##Class(const Class *C);
|
||||
#include "llvm/Frontend/OpenMP/OMP.inc"
|
||||
void VistOMPClauseWithPreInit(const OMPClauseWithPreInit *C);
|
||||
void VistOMPClauseWithPostUpdate(const OMPClauseWithPostUpdate *C);
|
||||
void VisitOMPClauseWithPreInit(const OMPClauseWithPreInit *C);
|
||||
void VisitOMPClauseWithPostUpdate(const OMPClauseWithPostUpdate *C);
|
||||
};
|
||||
|
||||
void OMPClauseProfiler::VistOMPClauseWithPreInit(
|
||||
void OMPClauseProfiler::VisitOMPClauseWithPreInit(
|
||||
const OMPClauseWithPreInit *C) {
|
||||
if (auto *S = C->getPreInitStmt())
|
||||
Profiler->VisitStmt(S);
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VistOMPClauseWithPostUpdate(
|
||||
void OMPClauseProfiler::VisitOMPClauseWithPostUpdate(
|
||||
const OMPClauseWithPostUpdate *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (auto *E = C->getPostUpdateExpr())
|
||||
Profiler->VisitStmt(E);
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VisitOMPIfClause(const OMPIfClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getCondition())
|
||||
Profiler->VisitStmt(C->getCondition());
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VisitOMPFinalClause(const OMPFinalClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getCondition())
|
||||
Profiler->VisitStmt(C->getCondition());
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VisitOMPNumThreadsClause(const OMPNumThreadsClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getNumThreads())
|
||||
Profiler->VisitStmt(C->getNumThreads());
|
||||
}
|
||||
@ -526,13 +526,13 @@ void OMPClauseProfiler::VisitOMPDetachClause(const OMPDetachClause *C) {
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VisitOMPNovariantsClause(const OMPNovariantsClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getCondition())
|
||||
Profiler->VisitStmt(C->getCondition());
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VisitOMPNocontextClause(const OMPNocontextClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getCondition())
|
||||
Profiler->VisitStmt(C->getCondition());
|
||||
}
|
||||
@ -568,7 +568,7 @@ void OMPClauseProfiler::VisitOMPMessageClause(const OMPMessageClause *C) {
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VisitOMPScheduleClause(const OMPScheduleClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (auto *S = C->getChunkSize())
|
||||
Profiler->VisitStmt(S);
|
||||
}
|
||||
@ -646,7 +646,7 @@ void OMPClauseProfiler::VisitOMPDestroyClause(const OMPDestroyClause *C) {
|
||||
}
|
||||
|
||||
void OMPClauseProfiler::VisitOMPFilterClause(const OMPFilterClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getThreadID())
|
||||
Profiler->VisitStmt(C->getThreadID());
|
||||
}
|
||||
@ -669,7 +669,7 @@ void OMPClauseProfiler::VisitOMPPrivateClause(const OMPPrivateClause *C) {
|
||||
void
|
||||
OMPClauseProfiler::VisitOMPFirstprivateClause(const OMPFirstprivateClause *C) {
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
for (auto *E : C->private_copies()) {
|
||||
if (E)
|
||||
Profiler->VisitStmt(E);
|
||||
@ -682,7 +682,7 @@ OMPClauseProfiler::VisitOMPFirstprivateClause(const OMPFirstprivateClause *C) {
|
||||
void
|
||||
OMPClauseProfiler::VisitOMPLastprivateClause(const OMPLastprivateClause *C) {
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPostUpdate(C);
|
||||
VisitOMPClauseWithPostUpdate(C);
|
||||
for (auto *E : C->source_exprs()) {
|
||||
if (E)
|
||||
Profiler->VisitStmt(E);
|
||||
@ -705,7 +705,7 @@ void OMPClauseProfiler::VisitOMPReductionClause(
|
||||
C->getQualifierLoc().getNestedNameSpecifier());
|
||||
Profiler->VisitName(C->getNameInfo().getName());
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPostUpdate(C);
|
||||
VisitOMPClauseWithPostUpdate(C);
|
||||
for (auto *E : C->privates()) {
|
||||
if (E)
|
||||
Profiler->VisitStmt(E);
|
||||
@ -743,7 +743,7 @@ void OMPClauseProfiler::VisitOMPTaskReductionClause(
|
||||
C->getQualifierLoc().getNestedNameSpecifier());
|
||||
Profiler->VisitName(C->getNameInfo().getName());
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPostUpdate(C);
|
||||
VisitOMPClauseWithPostUpdate(C);
|
||||
for (auto *E : C->privates()) {
|
||||
if (E)
|
||||
Profiler->VisitStmt(E);
|
||||
@ -767,7 +767,7 @@ void OMPClauseProfiler::VisitOMPInReductionClause(
|
||||
C->getQualifierLoc().getNestedNameSpecifier());
|
||||
Profiler->VisitName(C->getNameInfo().getName());
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPostUpdate(C);
|
||||
VisitOMPClauseWithPostUpdate(C);
|
||||
for (auto *E : C->privates()) {
|
||||
if (E)
|
||||
Profiler->VisitStmt(E);
|
||||
@ -791,7 +791,7 @@ void OMPClauseProfiler::VisitOMPInReductionClause(
|
||||
}
|
||||
void OMPClauseProfiler::VisitOMPLinearClause(const OMPLinearClause *C) {
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPostUpdate(C);
|
||||
VisitOMPClauseWithPostUpdate(C);
|
||||
for (auto *E : C->privates()) {
|
||||
if (E)
|
||||
Profiler->VisitStmt(E);
|
||||
@ -873,25 +873,25 @@ void OMPClauseProfiler::VisitOMPAllocateClause(const OMPAllocateClause *C) {
|
||||
}
|
||||
void OMPClauseProfiler::VisitOMPNumTeamsClause(const OMPNumTeamsClause *C) {
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
}
|
||||
void OMPClauseProfiler::VisitOMPThreadLimitClause(
|
||||
const OMPThreadLimitClause *C) {
|
||||
VisitOMPClauseList(C);
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
}
|
||||
void OMPClauseProfiler::VisitOMPPriorityClause(const OMPPriorityClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getPriority())
|
||||
Profiler->VisitStmt(C->getPriority());
|
||||
}
|
||||
void OMPClauseProfiler::VisitOMPGrainsizeClause(const OMPGrainsizeClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getGrainsize())
|
||||
Profiler->VisitStmt(C->getGrainsize());
|
||||
}
|
||||
void OMPClauseProfiler::VisitOMPNumTasksClause(const OMPNumTasksClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (C->getNumTasks())
|
||||
Profiler->VisitStmt(C->getNumTasks());
|
||||
}
|
||||
@ -952,7 +952,7 @@ void OMPClauseProfiler::VisitOMPOrderClause(const OMPOrderClause *C) {}
|
||||
void OMPClauseProfiler::VisitOMPBindClause(const OMPBindClause *C) {}
|
||||
void OMPClauseProfiler::VisitOMPXDynCGroupMemClause(
|
||||
const OMPXDynCGroupMemClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (Expr *Size = C->getSize())
|
||||
Profiler->VisitStmt(Size);
|
||||
}
|
||||
@ -1229,7 +1229,7 @@ void StmtProfiler::VisitOMPDistributeDirective(
|
||||
|
||||
void OMPClauseProfiler::VisitOMPDistScheduleClause(
|
||||
const OMPDistScheduleClause *C) {
|
||||
VistOMPClauseWithPreInit(C);
|
||||
VisitOMPClauseWithPreInit(C);
|
||||
if (auto *S = C->getChunkSize())
|
||||
Profiler->VisitStmt(S);
|
||||
}
|
||||
|
||||
@ -1627,7 +1627,7 @@ bool QualType::UseExcessPrecision(const ASTContext &Ctx) {
|
||||
switch (BT->getKind()) {
|
||||
case BuiltinType::Kind::Float16: {
|
||||
const TargetInfo &TI = Ctx.getTargetInfo();
|
||||
if (TI.hasFloat16Type() && !TI.hasLegalHalfType() &&
|
||||
if (TI.hasFloat16Type() && !TI.hasFastHalfType() &&
|
||||
Ctx.getLangOpts().getFloat16ExcessPrecision() !=
|
||||
Ctx.getLangOpts().ExcessPrecisionKind::FPP_None)
|
||||
return true;
|
||||
|
||||
@ -1344,6 +1344,41 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T> static SourceLocation getNodeLocation(const T &Node) {
|
||||
return Node.getBeginLoc();
|
||||
}
|
||||
|
||||
static SourceLocation getNodeLocation(const CXXCtorInitializer &Node) {
|
||||
return Node.getSourceLocation();
|
||||
}
|
||||
|
||||
static SourceLocation getNodeLocation(const TemplateArgumentLoc &Node) {
|
||||
return Node.getLocation();
|
||||
}
|
||||
|
||||
static SourceLocation getNodeLocation(const Attr &Node) {
|
||||
return Node.getLocation();
|
||||
}
|
||||
|
||||
bool isInSystemHeader(SourceLocation Loc) {
|
||||
const SourceManager &SM = getASTContext().getSourceManager();
|
||||
return SM.isInSystemHeader(Loc);
|
||||
}
|
||||
|
||||
template <typename T> bool shouldSkipNode(T &Node) {
|
||||
if (Options.IgnoreSystemHeaders && isInSystemHeader(getNodeLocation(Node)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T> bool shouldSkipNode(T *Node) {
|
||||
return (Node == nullptr) || shouldSkipNode(*Node);
|
||||
}
|
||||
|
||||
bool shouldSkipNode(QualType &) { return false; }
|
||||
|
||||
bool shouldSkipNode(NestedNameSpecifier &) { return false; }
|
||||
|
||||
/// Bucket to record map.
|
||||
///
|
||||
/// Used to get the appropriate bucket for each matcher.
|
||||
@ -1473,9 +1508,8 @@ bool MatchASTVisitor::objcClassIsDerivedFrom(
|
||||
}
|
||||
|
||||
bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
|
||||
if (!DeclNode) {
|
||||
if (shouldSkipNode(DeclNode))
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScopedTraversal =
|
||||
TraversingASTNodeNotSpelledInSource || DeclNode->isImplicit();
|
||||
@ -1503,9 +1537,9 @@ bool MatchASTVisitor::TraverseDecl(Decl *DeclNode) {
|
||||
}
|
||||
|
||||
bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
|
||||
if (!StmtNode) {
|
||||
if (shouldSkipNode(StmtNode))
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
|
||||
TraversingASTChildrenNotSpelledInSource;
|
||||
|
||||
@ -1515,6 +1549,9 @@ bool MatchASTVisitor::TraverseStmt(Stmt *StmtNode, DataRecursionQueue *Queue) {
|
||||
}
|
||||
|
||||
bool MatchASTVisitor::TraverseType(QualType TypeNode, bool TraverseQualifier) {
|
||||
if (shouldSkipNode(TypeNode))
|
||||
return true;
|
||||
|
||||
match(TypeNode);
|
||||
return RecursiveASTVisitor<MatchASTVisitor>::TraverseType(TypeNode,
|
||||
TraverseQualifier);
|
||||
@ -1522,6 +1559,8 @@ bool MatchASTVisitor::TraverseType(QualType TypeNode, bool TraverseQualifier) {
|
||||
|
||||
bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode,
|
||||
bool TraverseQualifier) {
|
||||
if (shouldSkipNode(TypeLocNode))
|
||||
return true;
|
||||
// The RecursiveASTVisitor only visits types if they're not within TypeLocs.
|
||||
// We still want to find those types via matchers, so we match them here. Note
|
||||
// that the TypeLocs are structurally a shadow-hierarchy to the expressed
|
||||
@ -1534,6 +1573,9 @@ bool MatchASTVisitor::TraverseTypeLoc(TypeLoc TypeLocNode,
|
||||
}
|
||||
|
||||
bool MatchASTVisitor::TraverseNestedNameSpecifier(NestedNameSpecifier NNS) {
|
||||
if (shouldSkipNode(NNS))
|
||||
return true;
|
||||
|
||||
match(NNS);
|
||||
return RecursiveASTVisitor<MatchASTVisitor>::TraverseNestedNameSpecifier(NNS);
|
||||
}
|
||||
@ -1543,6 +1585,9 @@ bool MatchASTVisitor::TraverseNestedNameSpecifierLoc(
|
||||
if (!NNS)
|
||||
return true;
|
||||
|
||||
if (shouldSkipNode(NNS))
|
||||
return true;
|
||||
|
||||
match(NNS);
|
||||
|
||||
// We only match the nested name specifier here (as opposed to traversing it)
|
||||
@ -1555,7 +1600,7 @@ bool MatchASTVisitor::TraverseNestedNameSpecifierLoc(
|
||||
|
||||
bool MatchASTVisitor::TraverseConstructorInitializer(
|
||||
CXXCtorInitializer *CtorInit) {
|
||||
if (!CtorInit)
|
||||
if (shouldSkipNode(CtorInit))
|
||||
return true;
|
||||
|
||||
bool ScopedTraversal = TraversingASTNodeNotSpelledInSource ||
|
||||
@ -1573,11 +1618,17 @@ bool MatchASTVisitor::TraverseConstructorInitializer(
|
||||
}
|
||||
|
||||
bool MatchASTVisitor::TraverseTemplateArgumentLoc(TemplateArgumentLoc Loc) {
|
||||
if (shouldSkipNode(Loc))
|
||||
return true;
|
||||
|
||||
match(Loc);
|
||||
return RecursiveASTVisitor<MatchASTVisitor>::TraverseTemplateArgumentLoc(Loc);
|
||||
}
|
||||
|
||||
bool MatchASTVisitor::TraverseAttr(Attr *AttrNode) {
|
||||
if (shouldSkipNode(AttrNode))
|
||||
return true;
|
||||
|
||||
match(*AttrNode);
|
||||
return RecursiveASTVisitor<MatchASTVisitor>::TraverseAttr(AttrNode);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ add_clang_library(clangAnalysisFlowSensitive
|
||||
DataflowAnalysisContext.cpp
|
||||
DataflowEnvironment.cpp
|
||||
Formula.cpp
|
||||
FormulaSerialization.cpp
|
||||
HTMLLogger.cpp
|
||||
Logger.cpp
|
||||
RecordOps.cpp
|
||||
|
||||
@ -208,6 +208,24 @@ bool DataflowAnalysisContext::equivalentFormulas(const Formula &Val1,
|
||||
return isUnsatisfiable(std::move(Constraints));
|
||||
}
|
||||
|
||||
llvm::DenseSet<Atom> DataflowAnalysisContext::collectDependencies(
|
||||
llvm::DenseSet<Atom> Tokens) const {
|
||||
// Use a worklist algorithm, with `Remaining` holding the worklist and
|
||||
// `Tokens` tracking which atoms have already been added to the worklist.
|
||||
std::vector<Atom> Remaining(Tokens.begin(), Tokens.end());
|
||||
while (!Remaining.empty()) {
|
||||
Atom CurrentToken = Remaining.back();
|
||||
Remaining.pop_back();
|
||||
if (auto DepsIt = FlowConditionDeps.find(CurrentToken);
|
||||
DepsIt != FlowConditionDeps.end())
|
||||
for (Atom A : DepsIt->second)
|
||||
if (Tokens.insert(A).second)
|
||||
Remaining.push_back(A);
|
||||
}
|
||||
|
||||
return Tokens;
|
||||
}
|
||||
|
||||
void DataflowAnalysisContext::addTransitiveFlowConditionConstraints(
|
||||
Atom Token, llvm::SetVector<const Formula *> &Constraints) {
|
||||
llvm::DenseSet<Atom> AddedTokens;
|
||||
@ -224,6 +242,8 @@ void DataflowAnalysisContext::addTransitiveFlowConditionConstraints(
|
||||
|
||||
auto ConstraintsIt = FlowConditionConstraints.find(Token);
|
||||
if (ConstraintsIt == FlowConditionConstraints.end()) {
|
||||
// The flow condition is unconstrained. Just add the atom directly, which
|
||||
// is equivalent to asserting it is true.
|
||||
Constraints.insert(&arena().makeAtomRef(Token));
|
||||
} else {
|
||||
// Bind flow condition token via `iff` to its set of constraints:
|
||||
@ -239,6 +259,65 @@ void DataflowAnalysisContext::addTransitiveFlowConditionConstraints(
|
||||
}
|
||||
}
|
||||
|
||||
static void getReferencedAtoms(const Formula &F,
|
||||
llvm::DenseSet<dataflow::Atom> &Refs) {
|
||||
switch (F.kind()) {
|
||||
case Formula::AtomRef:
|
||||
Refs.insert(F.getAtom());
|
||||
break;
|
||||
case Formula::Literal:
|
||||
break;
|
||||
case Formula::Not:
|
||||
getReferencedAtoms(*F.operands()[0], Refs);
|
||||
break;
|
||||
case Formula::And:
|
||||
case Formula::Or:
|
||||
case Formula::Implies:
|
||||
case Formula::Equal:
|
||||
ArrayRef<const Formula *> Operands = F.operands();
|
||||
getReferencedAtoms(*Operands[0], Refs);
|
||||
getReferencedAtoms(*Operands[1], Refs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleLogicalContext DataflowAnalysisContext::exportLogicalContext(
|
||||
llvm::DenseSet<dataflow::Atom> TargetTokens) const {
|
||||
SimpleLogicalContext LC;
|
||||
|
||||
if (Invariant != nullptr) {
|
||||
LC.Invariant = Invariant;
|
||||
getReferencedAtoms(*Invariant, TargetTokens);
|
||||
}
|
||||
|
||||
llvm::DenseSet<dataflow::Atom> Dependencies =
|
||||
collectDependencies(std::move(TargetTokens));
|
||||
|
||||
for (dataflow::Atom Token : Dependencies) {
|
||||
// Only process the token if it is constrained. Unconstrained tokens don't
|
||||
// have dependencies.
|
||||
const Formula *Constraints = FlowConditionConstraints.lookup(Token);
|
||||
if (Constraints == nullptr)
|
||||
continue;
|
||||
LC.TokenDefs[Token] = Constraints;
|
||||
|
||||
if (auto DepsIt = FlowConditionDeps.find(Token);
|
||||
DepsIt != FlowConditionDeps.end())
|
||||
LC.TokenDeps[Token] = DepsIt->second;
|
||||
}
|
||||
|
||||
return LC;
|
||||
}
|
||||
|
||||
void DataflowAnalysisContext::initLogicalContext(SimpleLogicalContext LC) {
|
||||
Invariant = LC.Invariant;
|
||||
FlowConditionConstraints = std::move(LC.TokenDefs);
|
||||
// TODO: The dependencies in `LC.TokenDeps` can be reconstructed from
|
||||
// `LC.TokenDefs`. Give the caller the option to reconstruct, rather than
|
||||
// providing them directly, to save caller space (memory/disk).
|
||||
FlowConditionDeps = std::move(LC.TokenDeps);
|
||||
}
|
||||
|
||||
static void printAtomList(const llvm::SmallVector<Atom> &Atoms,
|
||||
llvm::raw_ostream &OS) {
|
||||
OS << "(";
|
||||
|
||||
153
clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
Normal file
153
clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
//===- FormulaSerialization.cpp ---------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Analysis/FlowSensitive/FormulaSerialization.h"
|
||||
#include "clang/Analysis/FlowSensitive/Arena.h"
|
||||
#include "clang/Analysis/FlowSensitive/Formula.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace clang::dataflow {
|
||||
|
||||
// Returns the leading indicator of operation formulas. `AtomRef` and `Literal`
|
||||
// are handled differently.
|
||||
static char compactSigil(Formula::Kind K) {
|
||||
switch (K) {
|
||||
case Formula::AtomRef:
|
||||
case Formula::Literal:
|
||||
// No sigil.
|
||||
return '\0';
|
||||
case Formula::Not:
|
||||
return '!';
|
||||
case Formula::And:
|
||||
return '&';
|
||||
case Formula::Or:
|
||||
return '|';
|
||||
case Formula::Implies:
|
||||
return '>';
|
||||
case Formula::Equal:
|
||||
return '=';
|
||||
}
|
||||
llvm_unreachable("unhandled formula kind");
|
||||
}
|
||||
|
||||
void serializeFormula(const Formula &F, llvm::raw_ostream &OS) {
|
||||
switch (Formula::numOperands(F.kind())) {
|
||||
case 0:
|
||||
switch (F.kind()) {
|
||||
case Formula::AtomRef:
|
||||
OS << F.getAtom();
|
||||
break;
|
||||
case Formula::Literal:
|
||||
OS << (F.literal() ? 'T' : 'F');
|
||||
break;
|
||||
default:
|
||||
llvm_unreachable("unhandled formula kind");
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
OS << compactSigil(F.kind());
|
||||
serializeFormula(*F.operands()[0], OS);
|
||||
break;
|
||||
case 2:
|
||||
OS << compactSigil(F.kind());
|
||||
serializeFormula(*F.operands()[0], OS);
|
||||
serializeFormula(*F.operands()[1], OS);
|
||||
break;
|
||||
default:
|
||||
llvm_unreachable("unhandled formula arity");
|
||||
}
|
||||
}
|
||||
|
||||
static llvm::Expected<const Formula *>
|
||||
parsePrefix(llvm::StringRef &Str, Arena &A,
|
||||
llvm::DenseMap<unsigned, Atom> &AtomMap) {
|
||||
if (Str.empty())
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"unexpected end of input");
|
||||
|
||||
char Prefix = Str[0];
|
||||
Str = Str.drop_front();
|
||||
|
||||
switch (Prefix) {
|
||||
case 'T':
|
||||
return &A.makeLiteral(true);
|
||||
case 'F':
|
||||
return &A.makeLiteral(false);
|
||||
case 'V': {
|
||||
unsigned AtomID;
|
||||
if (Str.consumeInteger(10, AtomID))
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"expected atom id");
|
||||
auto [It, Inserted] = AtomMap.try_emplace(AtomID, Atom());
|
||||
if (Inserted)
|
||||
It->second = A.makeAtom();
|
||||
return &A.makeAtomRef(It->second);
|
||||
}
|
||||
case '!': {
|
||||
auto OperandOrErr = parsePrefix(Str, A, AtomMap);
|
||||
if (!OperandOrErr)
|
||||
return OperandOrErr.takeError();
|
||||
return &A.makeNot(**OperandOrErr);
|
||||
}
|
||||
case '&':
|
||||
case '|':
|
||||
case '>':
|
||||
case '=': {
|
||||
auto LeftOrErr = parsePrefix(Str, A, AtomMap);
|
||||
if (!LeftOrErr)
|
||||
return LeftOrErr.takeError();
|
||||
|
||||
auto RightOrErr = parsePrefix(Str, A, AtomMap);
|
||||
if (!RightOrErr)
|
||||
return RightOrErr.takeError();
|
||||
|
||||
const Formula &LHS = **LeftOrErr;
|
||||
const Formula &RHS = **RightOrErr;
|
||||
|
||||
switch (Prefix) {
|
||||
case '&':
|
||||
return &A.makeAnd(LHS, RHS);
|
||||
case '|':
|
||||
return &A.makeOr(LHS, RHS);
|
||||
case '>':
|
||||
return &A.makeImplies(LHS, RHS);
|
||||
case '=':
|
||||
return &A.makeEquals(LHS, RHS);
|
||||
default:
|
||||
llvm_unreachable("unexpected binary op");
|
||||
}
|
||||
}
|
||||
default:
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"unexpected prefix character: %c", Prefix);
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Expected<const Formula *>
|
||||
parseFormula(llvm::StringRef Str, Arena &A,
|
||||
llvm::DenseMap<unsigned, Atom> &AtomMap) {
|
||||
size_t OriginalSize = Str.size();
|
||||
llvm::Expected<const Formula *> F = parsePrefix(Str, A, AtomMap);
|
||||
if (!F)
|
||||
return F.takeError();
|
||||
if (!Str.empty())
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
("unexpected suffix of length: " +
|
||||
llvm::Twine(Str.size() - OriginalSize))
|
||||
.str());
|
||||
return F;
|
||||
}
|
||||
|
||||
} // namespace clang::dataflow
|
||||
@ -45,10 +45,11 @@ struct Loan {
|
||||
/// is represented as empty LoanSet
|
||||
LoanID ID;
|
||||
AccessPath Path;
|
||||
SourceLocation IssueLoc;
|
||||
/// The expression that creates the loan, e.g., &x.
|
||||
const Expr *IssueExpr;
|
||||
|
||||
Loan(LoanID id, AccessPath path, SourceLocation loc)
|
||||
: ID(id), Path(path), IssueLoc(loc) {}
|
||||
Loan(LoanID id, AccessPath path, const Expr *IssueExpr)
|
||||
: ID(id), Path(path), IssueExpr(IssueExpr) {}
|
||||
};
|
||||
|
||||
/// An Origin is a symbolic identifier that represents the set of possible
|
||||
@ -82,8 +83,8 @@ class LoanManager {
|
||||
public:
|
||||
LoanManager() = default;
|
||||
|
||||
Loan &addLoan(AccessPath Path, SourceLocation Loc) {
|
||||
AllLoans.emplace_back(getNextLoanID(), Path, Loc);
|
||||
Loan &addLoan(AccessPath Path, const Expr *IssueExpr) {
|
||||
AllLoans.emplace_back(getNextLoanID(), Path, IssueExpr);
|
||||
return AllLoans.back();
|
||||
}
|
||||
|
||||
@ -199,6 +200,8 @@ public:
|
||||
AssignOrigin,
|
||||
/// An origin escapes the function by flowing into the return value.
|
||||
ReturnOfOrigin,
|
||||
/// An origin is used (eg. dereferencing a pointer).
|
||||
Use,
|
||||
/// A marker for a specific point in the code, for testing.
|
||||
TestPoint,
|
||||
};
|
||||
@ -242,12 +245,17 @@ public:
|
||||
|
||||
class ExpireFact : public Fact {
|
||||
LoanID LID;
|
||||
SourceLocation ExpiryLoc;
|
||||
|
||||
public:
|
||||
static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; }
|
||||
|
||||
ExpireFact(LoanID LID) : Fact(Kind::Expire), LID(LID) {}
|
||||
ExpireFact(LoanID LID, SourceLocation ExpiryLoc)
|
||||
: Fact(Kind::Expire), LID(LID), ExpiryLoc(ExpiryLoc) {}
|
||||
|
||||
LoanID getLoanID() const { return LID; }
|
||||
SourceLocation getExpiryLoc() const { return ExpiryLoc; }
|
||||
|
||||
void dump(llvm::raw_ostream &OS) const override {
|
||||
OS << "Expire (LoanID: " << getLoanID() << ")\n";
|
||||
}
|
||||
@ -287,6 +295,24 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class UseFact : public Fact {
|
||||
OriginID UsedOrigin;
|
||||
const Expr *UseExpr;
|
||||
|
||||
public:
|
||||
static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
|
||||
|
||||
UseFact(OriginID UsedOrigin, const Expr *UseExpr)
|
||||
: Fact(Kind::Use), UsedOrigin(UsedOrigin), UseExpr(UseExpr) {}
|
||||
|
||||
OriginID getUsedOrigin() const { return UsedOrigin; }
|
||||
const Expr *getUseExpr() const { return UseExpr; }
|
||||
|
||||
void dump(llvm::raw_ostream &OS) const override {
|
||||
OS << "Use (OriginID: " << UsedOrigin << ")\n";
|
||||
}
|
||||
};
|
||||
|
||||
/// A dummy-fact used to mark a specific point in the code for testing.
|
||||
/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
|
||||
class TestPointFact : public Fact {
|
||||
@ -417,13 +443,17 @@ public:
|
||||
if (VD->hasLocalStorage()) {
|
||||
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*UO);
|
||||
AccessPath AddrOfLocalVarPath(VD);
|
||||
const Loan &L = FactMgr.getLoanMgr().addLoan(AddrOfLocalVarPath,
|
||||
UO->getOperatorLoc());
|
||||
const Loan &L =
|
||||
FactMgr.getLoanMgr().addLoan(AddrOfLocalVarPath, UO);
|
||||
CurrentBlockFacts.push_back(
|
||||
FactMgr.createFact<IssueFact>(L.ID, OID));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (UO->getOpcode() == UO_Deref) {
|
||||
// This is a pointer use, like '*p'.
|
||||
OriginID OID = FactMgr.getOriginMgr().get(*UO->getSubExpr());
|
||||
CurrentBlockFacts.push_back(FactMgr.createFact<UseFact>(OID, UO));
|
||||
}
|
||||
}
|
||||
|
||||
@ -492,7 +522,8 @@ private:
|
||||
// Check if the loan is for a stack variable and if that variable
|
||||
// is the one being destructed.
|
||||
if (LoanPath.D == DestructedVD)
|
||||
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(L.ID));
|
||||
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
|
||||
L.ID, DtorOpt.getTriggerStmt()->getEndLoc()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,6 +649,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); }
|
||||
|
||||
Lattice getInState(const CFGBlock *B) const { return InStates.lookup(B); }
|
||||
@ -665,6 +697,8 @@ private:
|
||||
return D->transfer(In, *F->getAs<AssignOriginFact>());
|
||||
case Fact::Kind::ReturnOfOrigin:
|
||||
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
|
||||
case Fact::Kind::Use:
|
||||
return D->transfer(In, *F->getAs<UseFact>());
|
||||
case Fact::Kind::TestPoint:
|
||||
return D->transfer(In, *F->getAs<TestPointFact>());
|
||||
}
|
||||
@ -676,6 +710,7 @@ public:
|
||||
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const AssignOriginFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const UseFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
|
||||
};
|
||||
|
||||
@ -693,6 +728,20 @@ static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
|
||||
return A;
|
||||
}
|
||||
|
||||
/// Checks if set A is a subset of set B.
|
||||
template <typename T>
|
||||
static bool isSubsetOf(const llvm::ImmutableSet<T> &A,
|
||||
const llvm::ImmutableSet<T> &B) {
|
||||
// Empty set is a subset of all sets.
|
||||
if (A.isEmpty())
|
||||
return true;
|
||||
|
||||
for (const T &Elem : A)
|
||||
if (!B.contains(Elem))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Computes the key-wise union of two ImmutableMaps.
|
||||
// TODO(opt): This key-wise join is a performance bottleneck. A more
|
||||
// efficient merge could be implemented using a Patricia Trie or HAMT
|
||||
@ -700,7 +749,7 @@ static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
|
||||
template <typename K, typename V, typename Joiner>
|
||||
static llvm::ImmutableMap<K, V>
|
||||
join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
|
||||
typename llvm::ImmutableMap<K, V>::Factory &F, Joiner joinValues) {
|
||||
typename llvm::ImmutableMap<K, V>::Factory &F, Joiner JoinValues) {
|
||||
if (A.getHeight() < B.getHeight())
|
||||
std::swap(A, B);
|
||||
|
||||
@ -710,7 +759,7 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
|
||||
const K &Key = Entry.first;
|
||||
const V &ValB = Entry.second;
|
||||
if (const V *ValA = A.lookup(Key))
|
||||
A = F.add(A, Key, joinValues(*ValA, ValB));
|
||||
A = F.add(A, Key, JoinValues(*ValA, ValB));
|
||||
else
|
||||
A = F.add(A, Key, ValB);
|
||||
}
|
||||
@ -723,17 +772,14 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
|
||||
// ========================================================================= //
|
||||
|
||||
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
|
||||
using ExpiredLoanMap = llvm::ImmutableMap<LoanID, const ExpireFact *>;
|
||||
|
||||
/// An object to hold the factories for immutable collections, ensuring
|
||||
/// that all created states share the same underlying memory management.
|
||||
struct LifetimeFactory {
|
||||
OriginLoanMap::Factory OriginMapFactory;
|
||||
LoanSet::Factory LoanSetFactory;
|
||||
|
||||
/// Creates a singleton set containing only the given loan ID.
|
||||
LoanSet createLoanSet(LoanID LID) {
|
||||
return LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID);
|
||||
}
|
||||
ExpiredLoanMap::Factory ExpiredLoanMapFactory;
|
||||
};
|
||||
|
||||
/// Represents the dataflow lattice for loan propagation.
|
||||
@ -774,13 +820,15 @@ struct LoanPropagationLattice {
|
||||
class LoanPropagationAnalysis
|
||||
: public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice,
|
||||
Direction::Forward> {
|
||||
|
||||
LifetimeFactory &Factory;
|
||||
OriginLoanMap::Factory &OriginLoanMapFactory;
|
||||
LoanSet::Factory &LoanSetFactory;
|
||||
|
||||
public:
|
||||
LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
|
||||
LifetimeFactory &Factory)
|
||||
: DataflowAnalysis(C, AC, F), Factory(Factory) {}
|
||||
LifetimeFactory &LFactory)
|
||||
: DataflowAnalysis(C, AC, F),
|
||||
OriginLoanMapFactory(LFactory.OriginMapFactory),
|
||||
LoanSetFactory(LFactory.LoanSetFactory) {}
|
||||
|
||||
using Base::transfer;
|
||||
|
||||
@ -792,9 +840,9 @@ public:
|
||||
// TODO(opt): Keep the state small by removing origins which become dead.
|
||||
Lattice join(Lattice A, Lattice B) {
|
||||
OriginLoanMap JoinedOrigins =
|
||||
utils::join(A.Origins, B.Origins, Factory.OriginMapFactory,
|
||||
[this](LoanSet S1, LoanSet S2) {
|
||||
return utils::join(S1, S2, Factory.LoanSetFactory);
|
||||
utils::join(A.Origins, B.Origins, OriginLoanMapFactory,
|
||||
[&](LoanSet S1, LoanSet S2) {
|
||||
return utils::join(S1, S2, LoanSetFactory);
|
||||
});
|
||||
return Lattice(JoinedOrigins);
|
||||
}
|
||||
@ -803,8 +851,9 @@ public:
|
||||
Lattice transfer(Lattice In, const IssueFact &F) {
|
||||
OriginID OID = F.getOriginID();
|
||||
LoanID LID = F.getLoanID();
|
||||
return LoanPropagationLattice(Factory.OriginMapFactory.add(
|
||||
In.Origins, OID, Factory.createLoanSet(LID)));
|
||||
return LoanPropagationLattice(OriginLoanMapFactory.add(
|
||||
In.Origins, OID,
|
||||
LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
|
||||
}
|
||||
|
||||
/// The destination origin's loan set is replaced by the source's.
|
||||
@ -814,7 +863,7 @@ public:
|
||||
OriginID SrcOID = F.getSrcOriginID();
|
||||
LoanSet SrcLoans = getLoans(In, SrcOID);
|
||||
return LoanPropagationLattice(
|
||||
Factory.OriginMapFactory.add(In.Origins, DestOID, SrcLoans));
|
||||
OriginLoanMapFactory.add(In.Origins, DestOID, SrcLoans));
|
||||
}
|
||||
|
||||
LoanSet getLoans(OriginID OID, ProgramPoint P) {
|
||||
@ -825,7 +874,7 @@ private:
|
||||
LoanSet getLoans(Lattice L, OriginID OID) {
|
||||
if (auto *Loans = L.Origins.lookup(OID))
|
||||
return *Loans;
|
||||
return Factory.LoanSetFactory.getEmptySet();
|
||||
return LoanSetFactory.getEmptySet();
|
||||
}
|
||||
};
|
||||
|
||||
@ -835,10 +884,11 @@ private:
|
||||
|
||||
/// The dataflow lattice for tracking the set of expired loans.
|
||||
struct ExpiredLattice {
|
||||
LoanSet Expired;
|
||||
/// Map from an expired `LoanID` to the `ExpireFact` that made it expire.
|
||||
ExpiredLoanMap Expired;
|
||||
|
||||
ExpiredLattice() : Expired(nullptr) {};
|
||||
explicit ExpiredLattice(LoanSet S) : Expired(S) {}
|
||||
explicit ExpiredLattice(ExpiredLoanMap M) : Expired(M) {}
|
||||
|
||||
bool operator==(const ExpiredLattice &Other) const {
|
||||
return Expired == Other.Expired;
|
||||
@ -851,8 +901,8 @@ struct ExpiredLattice {
|
||||
OS << "ExpiredLattice State:\n";
|
||||
if (Expired.isEmpty())
|
||||
OS << " <empty>\n";
|
||||
for (const LoanID &LID : Expired)
|
||||
OS << " Loan " << LID << " is expired\n";
|
||||
for (const auto &[ID, _] : Expired)
|
||||
OS << " Loan " << ID << " is expired\n";
|
||||
}
|
||||
};
|
||||
|
||||
@ -861,26 +911,31 @@ class ExpiredLoansAnalysis
|
||||
: public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice,
|
||||
Direction::Forward> {
|
||||
|
||||
LoanSet::Factory &Factory;
|
||||
ExpiredLoanMap::Factory &Factory;
|
||||
|
||||
public:
|
||||
ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
|
||||
LifetimeFactory &Factory)
|
||||
: DataflowAnalysis(C, AC, F), Factory(Factory.LoanSetFactory) {}
|
||||
: DataflowAnalysis(C, AC, F), Factory(Factory.ExpiredLoanMapFactory) {}
|
||||
|
||||
using Base::transfer;
|
||||
|
||||
StringRef getAnalysisName() const { return "ExpiredLoans"; }
|
||||
|
||||
Lattice getInitialState() { return Lattice(Factory.getEmptySet()); }
|
||||
Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); }
|
||||
|
||||
/// Merges two lattices by taking the union of the expired loan sets.
|
||||
Lattice join(Lattice L1, Lattice L2) const {
|
||||
return Lattice(utils::join(L1.Expired, L2.Expired, Factory));
|
||||
/// Merges two lattices by taking the union of the two expired loans.
|
||||
Lattice join(Lattice L1, Lattice L2) {
|
||||
return Lattice(
|
||||
utils::join(L1.Expired, L2.Expired, Factory,
|
||||
// Take the last expiry fact to make this hermetic.
|
||||
[](const ExpireFact *F1, const ExpireFact *F2) {
|
||||
return F1->getExpiryLoc() > F2->getExpiryLoc() ? F1 : F2;
|
||||
}));
|
||||
}
|
||||
|
||||
Lattice transfer(Lattice In, const ExpireFact &F) {
|
||||
return Lattice(Factory.add(In.Expired, F.getLoanID()));
|
||||
return Lattice(Factory.add(In.Expired, F.getLoanID(), &F));
|
||||
}
|
||||
|
||||
// Removes the loan from the set of expired loans.
|
||||
@ -912,15 +967,116 @@ public:
|
||||
Lattice transfer(Lattice In, const IssueFact &F) {
|
||||
return Lattice(Factory.remove(In.Expired, F.getLoanID()));
|
||||
}
|
||||
|
||||
ExpiredLoanMap getExpiredLoans(ProgramPoint P) { return getState(P).Expired; }
|
||||
};
|
||||
|
||||
// ========================================================================= //
|
||||
// TODO:
|
||||
// - Modify loan expiry analysis to answer `bool isExpired(Loan L, Point P)`
|
||||
// - Modify origin liveness analysis to answer `bool isLive(Origin O, Point P)`
|
||||
// - Using the above three to perform the final error reporting.
|
||||
// Lifetime checker and Error reporter
|
||||
// ========================================================================= //
|
||||
|
||||
/// Struct to store the complete context for a potential lifetime violation.
|
||||
struct PendingWarning {
|
||||
SourceLocation ExpiryLoc; // Where the loan expired.
|
||||
const Expr *UseExpr; // Where the origin holding this loan was used.
|
||||
Confidence ConfidenceLevel;
|
||||
};
|
||||
|
||||
class LifetimeChecker {
|
||||
private:
|
||||
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
|
||||
LoanPropagationAnalysis &LoanPropagation;
|
||||
ExpiredLoansAnalysis &ExpiredLoans;
|
||||
FactManager &FactMgr;
|
||||
AnalysisDeclContext &ADC;
|
||||
LifetimeSafetyReporter *Reporter;
|
||||
|
||||
public:
|
||||
LifetimeChecker(LoanPropagationAnalysis &LPA, ExpiredLoansAnalysis &ELA,
|
||||
FactManager &FM, AnalysisDeclContext &ADC,
|
||||
LifetimeSafetyReporter *Reporter)
|
||||
: LoanPropagation(LPA), ExpiredLoans(ELA), FactMgr(FM), ADC(ADC),
|
||||
Reporter(Reporter) {}
|
||||
|
||||
void run() {
|
||||
llvm::TimeTraceScope TimeProfile("LifetimeChecker");
|
||||
for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
|
||||
for (const Fact *F : FactMgr.getFacts(B))
|
||||
if (const auto *UF = F->getAs<UseFact>())
|
||||
checkUse(UF);
|
||||
issuePendingWarnings();
|
||||
}
|
||||
|
||||
/// Checks for use-after-free errors for a given use of an Origin.
|
||||
///
|
||||
/// This method is called for each 'UseFact' identified in the control flow
|
||||
/// graph. It determines if the loans held by the used origin have expired
|
||||
/// at the point of use.
|
||||
void checkUse(const UseFact *UF) {
|
||||
|
||||
OriginID O = UF->getUsedOrigin();
|
||||
|
||||
// Get the set of loans that the origin might hold at this program point.
|
||||
LoanSet HeldLoans = LoanPropagation.getLoans(O, UF);
|
||||
|
||||
// Get the set of all loans that have expired at this program point.
|
||||
ExpiredLoanMap AllExpiredLoans = ExpiredLoans.getExpiredLoans(UF);
|
||||
|
||||
// If the pointer holds no loans or no loans have expired, there's nothing
|
||||
// to check.
|
||||
if (HeldLoans.isEmpty() || AllExpiredLoans.isEmpty())
|
||||
return;
|
||||
|
||||
// Identify loans that which have expired but are held by the pointer. Using
|
||||
// them is a use-after-free.
|
||||
llvm::SmallVector<LoanID> DefaultedLoans;
|
||||
// A definite UaF error occurs if all loans the origin might hold have
|
||||
// expired.
|
||||
bool IsDefiniteError = true;
|
||||
for (LoanID L : HeldLoans) {
|
||||
if (AllExpiredLoans.contains(L))
|
||||
DefaultedLoans.push_back(L);
|
||||
else
|
||||
// If at least one loan is not expired, this use is not a definite UaF.
|
||||
IsDefiniteError = false;
|
||||
}
|
||||
// If there are no defaulted loans, the use is safe.
|
||||
if (DefaultedLoans.empty())
|
||||
return;
|
||||
|
||||
// Determine the confidence level of the error (definite or maybe).
|
||||
Confidence CurrentConfidence =
|
||||
IsDefiniteError ? Confidence::Definite : Confidence::Maybe;
|
||||
|
||||
// For each expired loan, create a pending warning.
|
||||
for (LoanID DefaultedLoan : DefaultedLoans) {
|
||||
// If we already have a warning for this loan with a higher or equal
|
||||
// confidence, skip this one.
|
||||
if (FinalWarningsMap.count(DefaultedLoan) &&
|
||||
CurrentConfidence <= FinalWarningsMap[DefaultedLoan].ConfidenceLevel)
|
||||
continue;
|
||||
|
||||
auto *EF = AllExpiredLoans.lookup(DefaultedLoan);
|
||||
assert(EF && "Could not find ExpireFact for an expired loan.");
|
||||
|
||||
FinalWarningsMap[DefaultedLoan] = {/*ExpiryLoc=*/(*EF)->getExpiryLoc(),
|
||||
/*UseExpr=*/UF->getUseExpr(),
|
||||
/*ConfidenceLevel=*/CurrentConfidence};
|
||||
}
|
||||
}
|
||||
|
||||
void issuePendingWarnings() {
|
||||
if (!Reporter)
|
||||
return;
|
||||
for (const auto &[LID, Warning] : FinalWarningsMap) {
|
||||
const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
|
||||
const Expr *IssueExpr = L.IssueExpr;
|
||||
Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr,
|
||||
Warning.ExpiryLoc, Warning.ConfidenceLevel);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================================================= //
|
||||
// LifetimeSafetyAnalysis Class Implementation
|
||||
// ========================================================================= //
|
||||
@ -928,8 +1084,9 @@ public:
|
||||
// We need this here for unique_ptr with forward declared class.
|
||||
LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
|
||||
|
||||
LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC)
|
||||
: AC(AC), Factory(std::make_unique<LifetimeFactory>()),
|
||||
LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter)
|
||||
: AC(AC), Reporter(Reporter), Factory(std::make_unique<LifetimeFactory>()),
|
||||
FactMgr(std::make_unique<FactManager>()) {}
|
||||
|
||||
void LifetimeSafetyAnalysis::run() {
|
||||
@ -952,6 +1109,8 @@ void LifetimeSafetyAnalysis::run() {
|
||||
/// blocks; only Decls are visible. Therefore, loans in a block that
|
||||
/// never reach an Origin associated with a Decl can be safely dropped by
|
||||
/// the analysis.
|
||||
/// 3. Collapse ExpireFacts belonging to same source location into a single
|
||||
/// Fact.
|
||||
LoanPropagation =
|
||||
std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
|
||||
LoanPropagation->run();
|
||||
@ -959,6 +1118,10 @@ void LifetimeSafetyAnalysis::run() {
|
||||
ExpiredLoans =
|
||||
std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory);
|
||||
ExpiredLoans->run();
|
||||
|
||||
LifetimeChecker Checker(*LoanPropagation, *ExpiredLoans, *FactMgr, AC,
|
||||
Reporter);
|
||||
Checker.run();
|
||||
}
|
||||
|
||||
LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
|
||||
@ -967,9 +1130,13 @@ LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
|
||||
return LoanPropagation->getLoans(OID, PP);
|
||||
}
|
||||
|
||||
LoanSet LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const {
|
||||
std::vector<LoanID>
|
||||
LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const {
|
||||
assert(ExpiredLoans && "ExpiredLoansAnalysis has not been run.");
|
||||
return ExpiredLoans->getState(PP).Expired;
|
||||
std::vector<LoanID> Result;
|
||||
for (const auto &pair : ExpiredLoans->getExpiredLoans(PP))
|
||||
Result.push_back(pair.first);
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::optional<OriginID>
|
||||
@ -1009,8 +1176,9 @@ llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const {
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC) {
|
||||
internal::LifetimeSafetyAnalysis Analysis(AC);
|
||||
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter) {
|
||||
internal::LifetimeSafetyAnalysis Analysis(AC, Reporter);
|
||||
Analysis.run();
|
||||
}
|
||||
} // namespace clang::lifetimes
|
||||
|
||||
@ -62,7 +62,7 @@ TargetInfo::TargetInfo(const llvm::Triple &T) : Triple(T) {
|
||||
TLSSupported = true;
|
||||
VLASupported = true;
|
||||
NoAsmVariants = false;
|
||||
HasLegalHalfType = false;
|
||||
HasFastHalfType = false;
|
||||
HalfArgsAndReturns = false;
|
||||
HasFloat128 = false;
|
||||
HasIbm128 = false;
|
||||
|
||||
@ -142,7 +142,7 @@ AArch64TargetInfo::AArch64TargetInfo(const llvm::Triple &Triple,
|
||||
AddrSpaceMap = &ARM64AddrSpaceMap;
|
||||
|
||||
// All AArch64 implementations support ARMv8 FP, which makes half a legal type.
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
HalfArgsAndReturns = true;
|
||||
HasFloat16 = true;
|
||||
HasStrictFP = true;
|
||||
|
||||
@ -251,7 +251,7 @@ AMDGPUTargetInfo::AMDGPUTargetInfo(const llvm::Triple &Triple,
|
||||
BFloat16Format = &llvm::APFloat::BFloat();
|
||||
}
|
||||
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
HasFloat16 = true;
|
||||
WavefrontSize = (GPUFeatures & llvm::AMDGPU::FEATURE_WAVE32) ? 32 : 64;
|
||||
|
||||
|
||||
@ -585,13 +585,13 @@ bool ARMTargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
|
||||
} else if (Feature == "+fp16") {
|
||||
HW_FP |= HW_FP_HP;
|
||||
} else if (Feature == "+fullfp16") {
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
} else if (Feature == "+dotprod") {
|
||||
DotProd = true;
|
||||
} else if (Feature == "+mve") {
|
||||
MVE |= MVE_INT;
|
||||
} else if (Feature == "+mve.fp") {
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
FPU |= FPARMV8;
|
||||
MVE |= MVE_INT | MVE_FP;
|
||||
HW_FP |= HW_FP_SP | HW_FP_HP;
|
||||
@ -1014,11 +1014,11 @@ void ARMTargetInfo::getTargetDefines(const LangOptions &Opts,
|
||||
Builder.defineMacro("__ARM_FP_FAST", "1");
|
||||
|
||||
// Armv8.2-A FP16 vector intrinsic
|
||||
if ((FPU & NeonFPU) && HasLegalHalfType)
|
||||
if ((FPU & NeonFPU) && HasFastHalfType)
|
||||
Builder.defineMacro("__ARM_FEATURE_FP16_VECTOR_ARITHMETIC", "1");
|
||||
|
||||
// Armv8.2-A FP16 scalar intrinsics
|
||||
if (HasLegalHalfType)
|
||||
if (HasFastHalfType)
|
||||
Builder.defineMacro("__ARM_FEATURE_FP16_SCALAR_ARITHMETIC", "1");
|
||||
|
||||
// Armv8.2-A dot product intrinsics
|
||||
|
||||
@ -59,7 +59,7 @@ public:
|
||||
VLASupported = false;
|
||||
AddrSpaceMap = &DirectXAddrSpaceMap;
|
||||
UseAddrSpaceMapMangling = true;
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
HasFloat16 = true;
|
||||
NoAsmVariants = true;
|
||||
PlatformMinVersion = Triple.getOSVersion();
|
||||
|
||||
@ -149,7 +149,7 @@ bool HexagonTargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
|
||||
HasAudio = true;
|
||||
}
|
||||
if (CPU.compare("hexagonv68") >= 0) {
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
HasFloat16 = true;
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -65,7 +65,7 @@ NVPTXTargetInfo::NVPTXTargetInfo(const llvm::Triple &Triple,
|
||||
GPU = OffloadArch::UNUSED;
|
||||
|
||||
// PTX supports f16 as a fundamental type.
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
HasFloat16 = true;
|
||||
|
||||
if (TargetPointerWidth == 32)
|
||||
|
||||
@ -427,7 +427,7 @@ bool RISCVTargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
|
||||
ABI = ISAInfo->computeDefaultABI().str();
|
||||
|
||||
if (ISAInfo->hasExtension("zfh") || ISAInfo->hasExtension("zhinx"))
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
|
||||
FastScalarUnalignedAccess =
|
||||
llvm::is_contained(Features, "+unaligned-scalar-mem");
|
||||
|
||||
@ -106,7 +106,7 @@ protected:
|
||||
LongWidth = LongAlign = 64;
|
||||
AddrSpaceMap = &SPIRDefIsPrivMap;
|
||||
UseAddrSpaceMapMangling = true;
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
HasFloat16 = true;
|
||||
// Define available target features
|
||||
// These must be defined in sorted order!
|
||||
@ -427,7 +427,7 @@ public:
|
||||
BFloat16Width = BFloat16Align = 16;
|
||||
BFloat16Format = &llvm::APFloat::BFloat();
|
||||
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
HasFloat16 = true;
|
||||
HalfArgsAndReturns = true;
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ public:
|
||||
// -ffloat16-excess-precision=none is given, no conversions will be made
|
||||
// and instead the backend will promote each half operation to float
|
||||
// individually.
|
||||
HasLegalHalfType = false;
|
||||
HasFastHalfType = false;
|
||||
|
||||
HasStrictFP = true;
|
||||
}
|
||||
|
||||
@ -348,7 +348,7 @@ bool X86TargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
|
||||
HasAVX512BF16 = true;
|
||||
} else if (Feature == "+avx512fp16") {
|
||||
HasAVX512FP16 = true;
|
||||
HasLegalHalfType = true;
|
||||
HasFastHalfType = true;
|
||||
} else if (Feature == "+avx512dq") {
|
||||
HasAVX512DQ = true;
|
||||
} else if (Feature == "+avx512bitalg") {
|
||||
|
||||
@ -84,6 +84,10 @@ public:
|
||||
llvm_unreachable("Unsupported format for long double");
|
||||
}
|
||||
|
||||
mlir::Type getPtrToVPtrType() {
|
||||
return getPointerTo(cir::VPtrType::get(getContext()));
|
||||
}
|
||||
|
||||
/// Get a CIR record kind from a AST declaration tag.
|
||||
cir::RecordType::RecordKind getRecordKind(const clang::TagTypeKind kind) {
|
||||
switch (kind) {
|
||||
@ -263,6 +267,9 @@ public:
|
||||
cir::ConstantOp getSInt32(int32_t c, mlir::Location loc) {
|
||||
return getConstantInt(loc, getSInt32Ty(), c);
|
||||
}
|
||||
cir::ConstantOp getUInt32(uint32_t c, mlir::Location loc) {
|
||||
return getConstantInt(loc, getUInt32Ty(), c);
|
||||
}
|
||||
|
||||
// Creates constant nullptr for pointer type ty.
|
||||
cir::ConstantOp getNullPtr(mlir::Type ty, mlir::Location loc) {
|
||||
|
||||
@ -312,6 +312,20 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
|
||||
case Builtin::BI__builtin_rotateright64:
|
||||
return emitRotate(e, /*isRotateLeft=*/false);
|
||||
|
||||
case Builtin::BI__builtin_return_address:
|
||||
case Builtin::BI__builtin_frame_address: {
|
||||
mlir::Location loc = getLoc(e->getExprLoc());
|
||||
llvm::APSInt level = e->getArg(0)->EvaluateKnownConstInt(getContext());
|
||||
if (builtinID == Builtin::BI__builtin_return_address) {
|
||||
return RValue::get(cir::ReturnAddrOp::create(
|
||||
builder, loc,
|
||||
builder.getConstAPInt(loc, builder.getUInt32Ty(), level)));
|
||||
}
|
||||
return RValue::get(cir::FrameAddrOp::create(
|
||||
builder, loc,
|
||||
builder.getConstAPInt(loc, builder.getUInt32Ty(), level)));
|
||||
}
|
||||
|
||||
case Builtin::BI__builtin_trap:
|
||||
emitTrap(loc, /*createNewBlock=*/true);
|
||||
return RValue::get(nullptr);
|
||||
|
||||
@ -289,7 +289,7 @@ void CIRGenFunction::initializeVTablePointer(mlir::Location loc,
|
||||
}
|
||||
|
||||
// Apply the offsets.
|
||||
Address vtableField = loadCXXThisAddress();
|
||||
Address classAddr = loadCXXThisAddress();
|
||||
if (!nonVirtualOffset.isZero() || virtualOffset) {
|
||||
cgm.errorNYI(loc,
|
||||
"initializeVTablePointer: non-virtual and virtual offset");
|
||||
@ -300,9 +300,9 @@ void CIRGenFunction::initializeVTablePointer(mlir::Location loc,
|
||||
// vtable field is derived from `this` pointer, therefore they should be in
|
||||
// the same addr space.
|
||||
assert(!cir::MissingFeatures::addressSpace());
|
||||
// TODO(cir): This should be cir.vtable.get_vptr.
|
||||
vtableField = builder.createElementBitCast(loc, vtableField,
|
||||
vtableAddressPoint.getType());
|
||||
auto vtablePtr = cir::VTableGetVPtrOp::create(
|
||||
builder, loc, builder.getPtrToVPtrType(), classAddr.getPointer());
|
||||
Address vtableField = Address(vtablePtr, classAddr.getAlignment());
|
||||
builder.createStore(loc, vtableAddressPoint, vtableField);
|
||||
assert(!cir::MissingFeatures::opTBAA());
|
||||
assert(!cir::MissingFeatures::createInvariantGroup());
|
||||
@ -657,6 +657,23 @@ Address CIRGenFunction::getAddressOfBaseClass(
|
||||
return value;
|
||||
}
|
||||
|
||||
mlir::Value CIRGenFunction::getVTablePtr(mlir::Location loc, Address thisAddr,
|
||||
const CXXRecordDecl *rd) {
|
||||
auto vtablePtr = cir::VTableGetVPtrOp::create(
|
||||
builder, loc, builder.getPtrToVPtrType(), thisAddr.getPointer());
|
||||
Address vtablePtrAddr = Address(vtablePtr, thisAddr.getAlignment());
|
||||
|
||||
auto vtable = builder.createLoad(loc, vtablePtrAddr);
|
||||
assert(!cir::MissingFeatures::opTBAA());
|
||||
|
||||
if (cgm.getCodeGenOpts().OptimizationLevel > 0 &&
|
||||
cgm.getCodeGenOpts().StrictVTablePointers) {
|
||||
assert(!cir::MissingFeatures::createInvariantGroup());
|
||||
}
|
||||
|
||||
return vtable;
|
||||
}
|
||||
|
||||
void CIRGenFunction::emitCXXConstructorCall(const clang::CXXConstructorDecl *d,
|
||||
clang::CXXCtorType type,
|
||||
bool forVirtualBase,
|
||||
|
||||
@ -80,7 +80,7 @@ public:
|
||||
// initializer or to propagate to another context; for example,
|
||||
// side effects, or emitting an initialization that requires a
|
||||
// reference to its current location.
|
||||
mlir::Attribute emitForMemory(mlir::Attribute c, QualType t);
|
||||
mlir::Attribute emitForMemory(mlir::Attribute c, QualType destType);
|
||||
|
||||
/// Try to emit the initializer of the given declaration as an abstract
|
||||
/// constant.
|
||||
@ -90,8 +90,9 @@ public:
|
||||
/// asserting that it succeeded. This is only safe to do when the
|
||||
/// expression is known to be a constant expression with either a fairly
|
||||
/// simple type or a known simple form.
|
||||
mlir::Attribute emitAbstract(const Expr *e, QualType destType);
|
||||
mlir::Attribute emitAbstract(SourceLocation loc, const APValue &value,
|
||||
QualType t);
|
||||
QualType destType);
|
||||
|
||||
mlir::Attribute tryEmitConstantExpr(const ConstantExpr *ce);
|
||||
|
||||
@ -101,6 +102,7 @@ public:
|
||||
|
||||
mlir::Attribute tryEmitPrivateForVarInit(const VarDecl &d);
|
||||
|
||||
mlir::TypedAttr tryEmitPrivate(const Expr *e, QualType destType);
|
||||
mlir::Attribute tryEmitPrivate(const APValue &value, QualType destType);
|
||||
mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType t);
|
||||
|
||||
|
||||
@ -710,6 +710,16 @@ mlir::Attribute ConstantEmitter::tryEmitPrivateForMemory(const APValue &value,
|
||||
return (c ? emitForMemory(c, destType) : nullptr);
|
||||
}
|
||||
|
||||
mlir::Attribute ConstantEmitter::emitAbstract(const Expr *e,
|
||||
QualType destType) {
|
||||
AbstractStateRAII state{*this, true};
|
||||
mlir::Attribute c = mlir::cast<mlir::Attribute>(tryEmitPrivate(e, destType));
|
||||
if (!c)
|
||||
cgm.errorNYI(e->getSourceRange(),
|
||||
"emitAbstract failed, emit null constaant");
|
||||
return c;
|
||||
}
|
||||
|
||||
mlir::Attribute ConstantEmitter::emitAbstract(SourceLocation loc,
|
||||
const APValue &value,
|
||||
QualType destType) {
|
||||
@ -731,6 +741,32 @@ mlir::Attribute ConstantEmitter::emitForMemory(mlir::Attribute c,
|
||||
return c;
|
||||
}
|
||||
|
||||
mlir::TypedAttr ConstantEmitter::tryEmitPrivate(const Expr *e,
|
||||
QualType destType) {
|
||||
assert(!destType->isVoidType() && "can't emit a void constant");
|
||||
|
||||
if (mlir::Attribute c =
|
||||
ConstExprEmitter(*this).Visit(const_cast<Expr *>(e), destType))
|
||||
return llvm::dyn_cast<mlir::TypedAttr>(c);
|
||||
|
||||
Expr::EvalResult result;
|
||||
|
||||
bool success = false;
|
||||
|
||||
if (destType->isReferenceType())
|
||||
success = e->EvaluateAsLValue(result, cgm.getASTContext());
|
||||
else
|
||||
success =
|
||||
e->EvaluateAsRValue(result, cgm.getASTContext(), inConstantContext);
|
||||
|
||||
if (success && !result.hasSideEffects()) {
|
||||
mlir::Attribute c = tryEmitPrivate(result.Val, destType);
|
||||
return llvm::dyn_cast<mlir::TypedAttr>(c);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value,
|
||||
QualType destType) {
|
||||
auto &builder = cgm.getBuilder();
|
||||
|
||||
@ -1120,6 +1120,8 @@ public:
|
||||
|
||||
mlir::LogicalResult emitFunctionBody(const clang::Stmt *body);
|
||||
|
||||
mlir::LogicalResult emitGotoStmt(const clang::GotoStmt &s);
|
||||
|
||||
void emitImplicitAssignmentOperatorBody(FunctionArgList &args);
|
||||
|
||||
void emitInitializerForField(clang::FieldDecl *field, LValue lhs,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user