aboutsummaryrefslogtreecommitdiff
path: root/clang/lib
diff options
context:
space:
mode:
Diffstat (limited to 'clang/lib')
-rw-r--r--clang/lib/AST/DeclTemplate.cpp17
-rw-r--r--clang/lib/AST/Expr.cpp27
-rw-r--r--clang/lib/Analysis/CMakeLists.txt3
-rw-r--r--clang/lib/Analysis/LifetimeAnnotations.cpp (renamed from clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp)8
-rw-r--r--clang/lib/Analysis/LifetimeSafety.cpp1546
-rw-r--r--clang/lib/Analysis/LifetimeSafety/CMakeLists.txt12
-rw-r--r--clang/lib/Analysis/LifetimeSafety/Checker.cpp130
-rw-r--r--clang/lib/Analysis/LifetimeSafety/Dataflow.h188
-rw-r--r--clang/lib/Analysis/LifetimeSafety/Facts.cpp102
-rw-r--r--clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp348
-rw-r--r--clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp77
-rw-r--r--clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp180
-rw-r--r--clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp138
-rw-r--r--clang/lib/Analysis/LifetimeSafety/Loans.cpp18
-rw-r--r--clang/lib/Analysis/LifetimeSafety/Origins.cpp89
-rw-r--r--clang/lib/Basic/Targets.cpp4
-rw-r--r--clang/lib/Basic/Targets/OSTargets.h17
-rw-r--r--clang/lib/Basic/Targets/WebAssembly.h21
-rw-r--r--clang/lib/CIR/CodeGen/CIRGenBuilder.h6
-rw-r--r--clang/lib/CIR/CodeGen/CIRGenException.cpp8
-rw-r--r--clang/lib/CIR/CodeGen/CIRGenFunction.h2
-rw-r--r--clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp17
-rw-r--r--clang/lib/CIR/CodeGen/CIRGenStmt.cpp3
-rw-r--r--clang/lib/CIR/Dialect/Transforms/CMakeLists.txt1
-rw-r--r--clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp41
-rw-r--r--clang/lib/CIR/Dialect/Transforms/LoweringPrepareCXXABI.h38
-rw-r--r--clang/lib/CIR/Dialect/Transforms/LoweringPrepareItaniumCXXABI.cpp126
-rw-r--r--clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp2
-rw-r--r--clang/lib/Driver/Driver.cpp2
-rw-r--r--clang/lib/Sema/AnalysisBasedWarnings.cpp2
-rw-r--r--clang/lib/Sema/CMakeLists.txt1
-rw-r--r--clang/lib/Sema/CheckExprLifetime.cpp2
-rw-r--r--clang/lib/Sema/SemaAMDGPU.cpp2
-rw-r--r--clang/lib/Sema/SemaAPINotes.cpp2
-rw-r--r--clang/lib/Sema/SemaConcept.cpp27
-rw-r--r--clang/lib/Sema/SemaExpr.cpp22
-rw-r--r--clang/lib/Sema/SemaOpenACC.cpp6
-rw-r--r--clang/lib/Sema/SemaOpenACCClause.cpp114
38 files changed, 1947 insertions, 1402 deletions
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index c0be986..2f7ae6d 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1670,20 +1670,25 @@ clang::getReplacedTemplateParameter(Decl *D, unsigned Index) {
auto P = CTSD->getSpecializedTemplateOrPartial();
TemplateParameterList *TPL;
if (const auto *CTPSD =
- dyn_cast<ClassTemplatePartialSpecializationDecl *>(P))
+ dyn_cast<ClassTemplatePartialSpecializationDecl *>(P)) {
TPL = CTPSD->getTemplateParameters();
- else
- TPL = cast<ClassTemplateDecl *>(P)->getTemplateParameters();
+ // FIXME: Obtain Args deduced for the partial specialization.
+ return {TPL->getParam(Index), {}};
+ }
+ TPL = cast<ClassTemplateDecl *>(P)->getTemplateParameters();
return {TPL->getParam(Index), CTSD->getTemplateArgs()[Index]};
}
case Decl::Kind::VarTemplateSpecialization: {
const auto *VTSD = cast<VarTemplateSpecializationDecl>(D);
auto P = VTSD->getSpecializedTemplateOrPartial();
TemplateParameterList *TPL;
- if (const auto *VTPSD = dyn_cast<VarTemplatePartialSpecializationDecl *>(P))
+ if (const auto *VTPSD =
+ dyn_cast<VarTemplatePartialSpecializationDecl *>(P)) {
TPL = VTPSD->getTemplateParameters();
- else
- TPL = cast<VarTemplateDecl *>(P)->getTemplateParameters();
+ // FIXME: Obtain Args deduced for the partial specialization.
+ return {TPL->getParam(Index), {}};
+ }
+ TPL = cast<VarTemplateDecl *>(P)->getTemplateParameters();
return {TPL->getParam(Index), VTSD->getTemplateArgs()[Index]};
}
case Decl::Kind::ClassTemplatePartialSpecialization:
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index f899b3c..597cbd8 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -5290,6 +5290,33 @@ QualType ArraySectionExpr::getBaseOriginalType(const Expr *Base) {
return OriginalTy;
}
+QualType ArraySectionExpr::getElementType() const {
+ QualType BaseTy = getBase()->IgnoreParenImpCasts()->getType();
+ // We only have to look into the array section exprs, else we will get the
+ // type of the base, which should already be valid.
+ if (auto *ASE = dyn_cast<ArraySectionExpr>(getBase()->IgnoreParenImpCasts()))
+ BaseTy = ASE->getElementType();
+
+ if (BaseTy->isAnyPointerType())
+ return BaseTy->getPointeeType();
+ if (BaseTy->isArrayType())
+ return BaseTy->castAsArrayTypeUnsafe()->getElementType();
+
+ // If this isn't a pointer or array, the base is a dependent expression, so
+ // just return the BaseTy anyway.
+ assert(BaseTy->isInstantiationDependentType());
+ return BaseTy;
+}
+
+QualType ArraySectionExpr::getBaseType() const {
+ // We only have to look into the array section exprs, else we will get the
+ // type of the base, which should already be valid.
+ if (auto *ASE = dyn_cast<ArraySectionExpr>(getBase()->IgnoreParenImpCasts()))
+ return ASE->getElementType();
+
+ return getBase()->IgnoreParenImpCasts()->getType();
+}
+
RecoveryExpr::RecoveryExpr(ASTContext &Ctx, QualType T, SourceLocation BeginLoc,
SourceLocation EndLoc, ArrayRef<Expr *> SubExprs)
: Expr(RecoveryExprClass, T.getNonReferenceType(),
diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt
index 1dbd415..5a26f3e 100644
--- a/clang/lib/Analysis/CMakeLists.txt
+++ b/clang/lib/Analysis/CMakeLists.txt
@@ -21,6 +21,8 @@ add_clang_library(clangAnalysis
FixitUtil.cpp
IntervalPartition.cpp
IssueHash.cpp
+ LifetimeAnnotations.cpp
+ LifetimeSafety.cpp
LiveVariables.cpp
MacroExpansionContext.cpp
ObjCNoReturn.cpp
@@ -49,4 +51,3 @@ add_clang_library(clangAnalysis
add_subdirectory(plugins)
add_subdirectory(FlowSensitive)
-add_subdirectory(LifetimeSafety)
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeAnnotations.cpp
index ad61a42..e791224 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeAnnotations.cpp
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
@@ -13,7 +13,8 @@
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
-namespace clang::lifetimes {
+namespace clang {
+namespace lifetimes {
const FunctionDecl *
getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
@@ -70,4 +71,5 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
return isNormalAssignmentOperator(FD);
}
-} // namespace clang::lifetimes
+} // namespace lifetimes
+} // namespace clang
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
new file mode 100644
index 0000000..6196ec3
--- /dev/null
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -0,0 +1,1546 @@
+//===- LifetimeSafety.cpp - C++ Lifetime Safety Analysis -*--------- 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/Analyses/LifetimeSafety.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/AST/Type.h"
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
+#include "clang/Analysis/Analyses/PostOrderCFGView.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
+#include "clang/Analysis/CFG.h"
+#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
+#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/ImmutableMap.h"
+#include "llvm/ADT/ImmutableSet.h"
+#include "llvm/ADT/PointerUnion.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/TimeProfiler.h"
+#include <cstdint>
+#include <memory>
+#include <optional>
+
+namespace clang::lifetimes {
+namespace internal {
+
+/// Represents the storage location being borrowed, e.g., a specific stack
+/// variable.
+/// TODO: Model access paths of other types, e.g., s.field, heap and globals.
+struct AccessPath {
+ const clang::ValueDecl *D;
+
+ AccessPath(const clang::ValueDecl *D) : D(D) {}
+};
+
+/// Information about a single borrow, or "Loan". A loan is created when a
+/// reference or pointer is created.
+struct Loan {
+ /// TODO: Represent opaque loans.
+ /// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it
+ /// is represented as empty LoanSet
+ LoanID ID;
+ AccessPath Path;
+ /// The expression that creates the loan, e.g., &x.
+ const Expr *IssueExpr;
+
+ Loan(LoanID id, AccessPath path, const Expr *IssueExpr)
+ : ID(id), Path(path), IssueExpr(IssueExpr) {}
+
+ void dump(llvm::raw_ostream &OS) const {
+ OS << ID << " (Path: ";
+ OS << Path.D->getNameAsString() << ")";
+ }
+};
+
+/// An Origin is a symbolic identifier that represents the set of possible
+/// loans a pointer-like object could hold at any given time.
+/// TODO: Enhance the origin model to handle complex types, pointer
+/// indirection and reborrowing. The plan is to move from a single origin per
+/// variable/expression to a "list of origins" governed by the Type.
+/// For example, the type 'int**' would have two origins.
+/// See discussion:
+/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238
+struct Origin {
+ OriginID ID;
+ /// A pointer to the AST node that this origin represents. This union
+ /// distinguishes between origins from declarations (variables or parameters)
+ /// and origins from expressions.
+ llvm::PointerUnion<const clang::ValueDecl *, const clang::Expr *> Ptr;
+
+ Origin(OriginID ID, const clang::ValueDecl *D) : ID(ID), Ptr(D) {}
+ Origin(OriginID ID, const clang::Expr *E) : ID(ID), Ptr(E) {}
+
+ const clang::ValueDecl *getDecl() const {
+ return Ptr.dyn_cast<const clang::ValueDecl *>();
+ }
+ const clang::Expr *getExpr() const {
+ return Ptr.dyn_cast<const clang::Expr *>();
+ }
+};
+
+/// Manages the creation, storage and retrieval of loans.
+class LoanManager {
+public:
+ LoanManager() = default;
+
+ Loan &addLoan(AccessPath Path, const Expr *IssueExpr) {
+ AllLoans.emplace_back(getNextLoanID(), Path, IssueExpr);
+ return AllLoans.back();
+ }
+
+ const Loan &getLoan(LoanID ID) const {
+ assert(ID.Value < AllLoans.size());
+ return AllLoans[ID.Value];
+ }
+ llvm::ArrayRef<Loan> getLoans() const { return AllLoans; }
+
+private:
+ LoanID getNextLoanID() { return NextLoanID++; }
+
+ LoanID NextLoanID{0};
+ /// TODO(opt): Profile and evaluate the usefullness of small buffer
+ /// optimisation.
+ llvm::SmallVector<Loan> AllLoans;
+};
+
+/// Manages the creation, storage, and retrieval of origins for pointer-like
+/// variables and expressions.
+class OriginManager {
+public:
+ OriginManager() = default;
+
+ Origin &addOrigin(OriginID ID, const clang::ValueDecl &D) {
+ AllOrigins.emplace_back(ID, &D);
+ return AllOrigins.back();
+ }
+ Origin &addOrigin(OriginID ID, const clang::Expr &E) {
+ AllOrigins.emplace_back(ID, &E);
+ return AllOrigins.back();
+ }
+
+ // TODO: Mark this method as const once we remove the call to getOrCreate.
+ OriginID get(const Expr &E) {
+ auto It = ExprToOriginID.find(&E);
+ if (It != ExprToOriginID.end())
+ return It->second;
+ // If the expression itself has no specific origin, and it's a reference
+ // to a declaration, its origin is that of the declaration it refers to.
+ // For pointer types, where we don't pre-emptively create an origin for the
+ // DeclRefExpr itself.
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(&E))
+ return get(*DRE->getDecl());
+ // TODO: This should be an assert(It != ExprToOriginID.end()). The current
+ // implementation falls back to getOrCreate to avoid crashing on
+ // yet-unhandled pointer expressions, creating an empty origin for them.
+ return getOrCreate(E);
+ }
+
+ OriginID get(const ValueDecl &D) {
+ auto It = DeclToOriginID.find(&D);
+ // TODO: This should be an assert(It != DeclToOriginID.end()). The current
+ // implementation falls back to getOrCreate to avoid crashing on
+ // yet-unhandled pointer expressions, creating an empty origin for them.
+ if (It == DeclToOriginID.end())
+ return getOrCreate(D);
+
+ return It->second;
+ }
+
+ OriginID getOrCreate(const Expr &E) {
+ auto It = ExprToOriginID.find(&E);
+ if (It != ExprToOriginID.end())
+ return It->second;
+
+ OriginID NewID = getNextOriginID();
+ addOrigin(NewID, E);
+ ExprToOriginID[&E] = NewID;
+ return NewID;
+ }
+
+ const Origin &getOrigin(OriginID ID) const {
+ assert(ID.Value < AllOrigins.size());
+ return AllOrigins[ID.Value];
+ }
+
+ llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }
+
+ OriginID getOrCreate(const ValueDecl &D) {
+ auto It = DeclToOriginID.find(&D);
+ if (It != DeclToOriginID.end())
+ return It->second;
+ OriginID NewID = getNextOriginID();
+ addOrigin(NewID, D);
+ DeclToOriginID[&D] = NewID;
+ return NewID;
+ }
+
+ void dump(OriginID OID, llvm::raw_ostream &OS) const {
+ OS << OID << " (";
+ Origin O = getOrigin(OID);
+ if (const ValueDecl *VD = O.getDecl())
+ OS << "Decl: " << VD->getNameAsString();
+ else if (const Expr *E = O.getExpr())
+ OS << "Expr: " << E->getStmtClassName();
+ else
+ OS << "Unknown";
+ OS << ")";
+ }
+
+private:
+ OriginID getNextOriginID() { return NextOriginID++; }
+
+ OriginID NextOriginID{0};
+ /// TODO(opt): Profile and evaluate the usefullness of small buffer
+ /// optimisation.
+ llvm::SmallVector<Origin> AllOrigins;
+ llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
+ llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
+};
+
+/// An abstract base class for a single, atomic lifetime-relevant event.
+class Fact {
+
+public:
+ enum class Kind : uint8_t {
+ /// A new loan is issued from a borrow expression (e.g., &x).
+ Issue,
+ /// A loan expires as its underlying storage is freed (e.g., variable goes
+ /// out of scope).
+ Expire,
+ /// An origin is propagated from a source to a destination (e.g., p = q).
+ /// This can also optionally kill the destination origin before flowing into
+ /// it. Otherwise, the source's loan set is merged into the destination's
+ /// loan set.
+ OriginFlow,
+ /// An origin escapes the function by flowing into the return value.
+ ReturnOfOrigin,
+ /// An origin is used (eg. appears as l-value expression like DeclRefExpr).
+ Use,
+ /// A marker for a specific point in the code, for testing.
+ TestPoint,
+ };
+
+private:
+ Kind K;
+
+protected:
+ Fact(Kind K) : K(K) {}
+
+public:
+ virtual ~Fact() = default;
+ Kind getKind() const { return K; }
+
+ template <typename T> const T *getAs() const {
+ if (T::classof(this))
+ return static_cast<const T *>(this);
+ return nullptr;
+ }
+
+ virtual void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &) const {
+ OS << "Fact (Kind: " << static_cast<int>(K) << ")\n";
+ }
+};
+
+class IssueFact : public Fact {
+ LoanID LID;
+ OriginID OID;
+
+public:
+ static bool classof(const Fact *F) { return F->getKind() == Kind::Issue; }
+
+ IssueFact(LoanID LID, OriginID OID) : Fact(Kind::Issue), LID(LID), OID(OID) {}
+ LoanID getLoanID() const { return LID; }
+ OriginID getOriginID() const { return OID; }
+ void dump(llvm::raw_ostream &OS, const LoanManager &LM,
+ const OriginManager &OM) const override {
+ OS << "Issue (";
+ LM.getLoan(getLoanID()).dump(OS);
+ OS << ", ToOrigin: ";
+ OM.dump(getOriginID(), OS);
+ OS << ")\n";
+ }
+};
+
+class ExpireFact : public Fact {
+ LoanID LID;
+ SourceLocation ExpiryLoc;
+
+public:
+ static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; }
+
+ 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 LoanManager &LM,
+ const OriginManager &) const override {
+ OS << "Expire (";
+ LM.getLoan(getLoanID()).dump(OS);
+ OS << ")\n";
+ }
+};
+
+class OriginFlowFact : public Fact {
+ OriginID OIDDest;
+ OriginID OIDSrc;
+ // True if the destination origin should be killed (i.e., its current loans
+ // cleared) before the source origin's loans are flowed into it.
+ bool KillDest;
+
+public:
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::OriginFlow;
+ }
+
+ OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
+ : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc),
+ KillDest(KillDest) {}
+
+ OriginID getDestOriginID() const { return OIDDest; }
+ OriginID getSrcOriginID() const { return OIDSrc; }
+ bool getKillDest() const { return KillDest; }
+
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override {
+ OS << "OriginFlow (Dest: ";
+ OM.dump(getDestOriginID(), OS);
+ OS << ", Src: ";
+ OM.dump(getSrcOriginID(), OS);
+ OS << (getKillDest() ? "" : ", Merge");
+ OS << ")\n";
+ }
+};
+
+class ReturnOfOriginFact : public Fact {
+ OriginID OID;
+
+public:
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::ReturnOfOrigin;
+ }
+
+ ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {}
+ OriginID getReturnedOriginID() const { return OID; }
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override {
+ OS << "ReturnOfOrigin (";
+ OM.dump(getReturnedOriginID(), OS);
+ OS << ")\n";
+ }
+};
+
+class UseFact : public Fact {
+ const Expr *UseExpr;
+ // True if this use is a write operation (e.g., left-hand side of assignment).
+ // Write operations are exempted from use-after-free checks.
+ bool IsWritten = false;
+
+public:
+ static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
+
+ UseFact(const Expr *UseExpr) : Fact(Kind::Use), UseExpr(UseExpr) {}
+
+ OriginID getUsedOrigin(const OriginManager &OM) const {
+ // TODO: Remove const cast and make OriginManager::get as const.
+ return const_cast<OriginManager &>(OM).get(*UseExpr);
+ }
+ const Expr *getUseExpr() const { return UseExpr; }
+ void markAsWritten() { IsWritten = true; }
+ bool isWritten() const { return IsWritten; }
+
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override {
+ OS << "Use (";
+ OM.dump(getUsedOrigin(OM), OS);
+ OS << ", " << (isWritten() ? "Write" : "Read") << ")\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 {
+ StringRef Annotation;
+
+public:
+ static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; }
+
+ explicit TestPointFact(StringRef Annotation)
+ : Fact(Kind::TestPoint), Annotation(Annotation) {}
+
+ StringRef getAnnotation() const { return Annotation; }
+
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &) const override {
+ OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
+ }
+};
+
+class FactManager {
+public:
+ llvm::ArrayRef<const Fact *> getFacts(const CFGBlock *B) const {
+ auto It = BlockToFactsMap.find(B);
+ if (It != BlockToFactsMap.end())
+ return It->second;
+ return {};
+ }
+
+ void addBlockFacts(const CFGBlock *B, llvm::ArrayRef<Fact *> NewFacts) {
+ if (!NewFacts.empty())
+ BlockToFactsMap[B].assign(NewFacts.begin(), NewFacts.end());
+ }
+
+ template <typename FactType, typename... Args>
+ FactType *createFact(Args &&...args) {
+ void *Mem = FactAllocator.Allocate<FactType>();
+ return new (Mem) FactType(std::forward<Args>(args)...);
+ }
+
+ void dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
+ llvm::dbgs() << "==========================================\n";
+ llvm::dbgs() << " Lifetime Analysis Facts:\n";
+ llvm::dbgs() << "==========================================\n";
+ if (const Decl *D = AC.getDecl())
+ if (const auto *ND = dyn_cast<NamedDecl>(D))
+ llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n";
+ // Print blocks in the order as they appear in code for a stable ordering.
+ for (const CFGBlock *B : *AC.getAnalysis<PostOrderCFGView>()) {
+ llvm::dbgs() << " Block B" << B->getBlockID() << ":\n";
+ auto It = BlockToFactsMap.find(B);
+ if (It != BlockToFactsMap.end()) {
+ for (const Fact *F : It->second) {
+ llvm::dbgs() << " ";
+ F->dump(llvm::dbgs(), LoanMgr, OriginMgr);
+ }
+ }
+ llvm::dbgs() << " End of Block\n";
+ }
+ }
+
+ LoanManager &getLoanMgr() { return LoanMgr; }
+ OriginManager &getOriginMgr() { return OriginMgr; }
+
+private:
+ LoanManager LoanMgr;
+ OriginManager OriginMgr;
+ llvm::DenseMap<const clang::CFGBlock *, llvm::SmallVector<const Fact *>>
+ BlockToFactsMap;
+ llvm::BumpPtrAllocator FactAllocator;
+};
+
+class FactGenerator : public ConstStmtVisitor<FactGenerator> {
+ using Base = ConstStmtVisitor<FactGenerator>;
+
+public:
+ FactGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
+ : FactMgr(FactMgr), AC(AC) {}
+
+ void run() {
+ llvm::TimeTraceScope TimeProfile("FactGenerator");
+ // Iterate through the CFG blocks in reverse post-order to ensure that
+ // initializations and destructions are processed in the correct sequence.
+ for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
+ CurrentBlockFacts.clear();
+ for (unsigned I = 0; I < Block->size(); ++I) {
+ const CFGElement &Element = Block->Elements[I];
+ if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
+ Visit(CS->getStmt());
+ else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
+ Element.getAs<CFGAutomaticObjDtor>())
+ handleDestructor(*DtorOpt);
+ }
+ FactMgr.addBlockFacts(Block, CurrentBlockFacts);
+ }
+ }
+
+ void VisitDeclStmt(const DeclStmt *DS) {
+ for (const Decl *D : DS->decls())
+ if (const auto *VD = dyn_cast<VarDecl>(D))
+ if (hasOrigin(VD))
+ if (const Expr *InitExpr = VD->getInit())
+ killAndFlowOrigin(*VD, *InitExpr);
+ }
+
+ void VisitDeclRefExpr(const DeclRefExpr *DRE) {
+ handleUse(DRE);
+ // For non-pointer/non-view types, a reference to the variable's storage
+ // is a borrow. We create a loan for it.
+ // For pointer/view types, we stick to the existing model for now and do
+ // not create an extra origin for the l-value expression itself.
+
+ // TODO: A single origin for a `DeclRefExpr` for a pointer or view type is
+ // not sufficient to model the different levels of indirection. The current
+ // single-origin model cannot distinguish between a loan to the variable's
+ // storage and a loan to what it points to. A multi-origin model would be
+ // required for this.
+ if (!isPointerType(DRE->getType())) {
+ if (const Loan *L = createLoan(DRE)) {
+ OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE);
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->ID, ExprOID));
+ }
+ }
+ }
+
+ void VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
+ if (isGslPointerType(CCE->getType())) {
+ handleGSLPointerConstruction(CCE);
+ return;
+ }
+ }
+
+ void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
+ // Specifically for conversion operators,
+ // like `std::string_view p = std::string{};`
+ if (isGslPointerType(MCE->getType()) &&
+ isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
+ // The argument is the implicit object itself.
+ handleFunctionCall(MCE, MCE->getMethodDecl(),
+ {MCE->getImplicitObjectArgument()},
+ /*IsGslConstruction=*/true);
+ }
+ if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
+ // Construct the argument list, with the implicit 'this' object as the
+ // first argument.
+ llvm::SmallVector<const Expr *, 4> Args;
+ Args.push_back(MCE->getImplicitObjectArgument());
+ Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs());
+
+ handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false);
+ }
+ }
+
+ void VisitCallExpr(const CallExpr *CE) {
+ handleFunctionCall(CE, CE->getDirectCallee(),
+ {CE->getArgs(), CE->getNumArgs()});
+ }
+
+ void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
+ /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
+ /// pointers can use the same type of loan.
+ FactMgr.getOriginMgr().getOrCreate(*N);
+ }
+
+ void VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
+ if (!hasOrigin(ICE))
+ return;
+ // An ImplicitCastExpr node itself gets an origin, which flows from the
+ // origin of its sub-expression (after stripping its own parens/casts).
+ killAndFlowOrigin(*ICE, *ICE->getSubExpr());
+ }
+
+ void VisitUnaryOperator(const UnaryOperator *UO) {
+ if (UO->getOpcode() == UO_AddrOf) {
+ const Expr *SubExpr = UO->getSubExpr();
+ // Taking address of a pointer-type expression is not yet supported and
+ // will be supported in multi-origin model.
+ if (isPointerType(SubExpr->getType()))
+ return;
+ // The origin of an address-of expression (e.g., &x) is the origin of
+ // its sub-expression (x). This fact will cause the dataflow analysis
+ // to propagate any loans held by the sub-expression's origin to the
+ // origin of this UnaryOperator expression.
+ killAndFlowOrigin(*UO, *SubExpr);
+ }
+ }
+
+ void VisitReturnStmt(const ReturnStmt *RS) {
+ if (const Expr *RetExpr = RS->getRetValue()) {
+ if (hasOrigin(RetExpr)) {
+ OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<ReturnOfOriginFact>(OID));
+ }
+ }
+ }
+
+ void VisitBinaryOperator(const BinaryOperator *BO) {
+ if (BO->isAssignmentOp())
+ handleAssignment(BO->getLHS(), BO->getRHS());
+ }
+
+ void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
+ // Assignment operators have special "kill-then-propagate" semantics
+ // and are handled separately.
+ if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) {
+ handleAssignment(OCE->getArg(0), OCE->getArg(1));
+ return;
+ }
+ handleFunctionCall(OCE, OCE->getDirectCallee(),
+ {OCE->getArgs(), OCE->getNumArgs()},
+ /*IsGslConstruction=*/false);
+ }
+
+ void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) {
+ // Check if this is a test point marker. If so, we are done with this
+ // expression.
+ if (handleTestPoint(FCE))
+ return;
+ if (isGslPointerType(FCE->getType()))
+ killAndFlowOrigin(*FCE, *FCE->getSubExpr());
+ }
+
+ void VisitInitListExpr(const InitListExpr *ILE) {
+ if (!hasOrigin(ILE))
+ return;
+ // For list initialization with a single element, like `View{...}`, the
+ // origin of the list itself is the origin of its single element.
+ if (ILE->getNumInits() == 1)
+ killAndFlowOrigin(*ILE, *ILE->getInit(0));
+ }
+
+ void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) {
+ if (!hasOrigin(MTE))
+ return;
+ // A temporary object's origin is the same as the origin of the
+ // expression that initializes it.
+ killAndFlowOrigin(*MTE, *MTE->getSubExpr());
+ }
+
+ void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
+ /// TODO: Also handle trivial destructors (e.g., for `int`
+ /// variables) which will never have a CFGAutomaticObjDtor node.
+ /// TODO: Handle loans to temporaries.
+ /// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the
+ /// lifetime ends.
+ const VarDecl *DestructedVD = DtorOpt.getVarDecl();
+ if (!DestructedVD)
+ return;
+ // Iterate through all loans to see if any expire.
+ /// TODO(opt): Do better than a linear search to find loans associated with
+ /// 'DestructedVD'.
+ for (const Loan &L : FactMgr.getLoanMgr().getLoans()) {
+ const AccessPath &LoanPath = L.Path;
+ // 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, DtorOpt.getTriggerStmt()->getEndLoc()));
+ }
+ }
+
+private:
+ static bool isGslPointerType(QualType QT) {
+ if (const auto *RD = QT->getAsCXXRecordDecl()) {
+ // We need to check the template definition for specializations.
+ if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+ return CTSD->getSpecializedTemplate()
+ ->getTemplatedDecl()
+ ->hasAttr<PointerAttr>();
+ return RD->hasAttr<PointerAttr>();
+ }
+ return false;
+ }
+
+ static bool isPointerType(QualType QT) {
+ return QT->isPointerOrReferenceType() || isGslPointerType(QT);
+ }
+ // Check if a type has an origin.
+ static bool hasOrigin(const Expr *E) {
+ return E->isGLValue() || isPointerType(E->getType());
+ }
+
+ static bool hasOrigin(const VarDecl *VD) {
+ return isPointerType(VD->getType());
+ }
+
+ void handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
+ assert(isGslPointerType(CCE->getType()));
+ if (CCE->getNumArgs() != 1)
+ return;
+ if (hasOrigin(CCE->getArg(0)))
+ killAndFlowOrigin(*CCE, *CCE->getArg(0));
+ else
+ // This could be a new borrow.
+ handleFunctionCall(CCE, CCE->getConstructor(),
+ {CCE->getArgs(), CCE->getNumArgs()},
+ /*IsGslConstruction=*/true);
+ }
+
+ /// Checks if a call-like expression creates a borrow by passing a value to a
+ /// reference parameter, creating an IssueFact if it does.
+ /// \param IsGslConstruction True if this is a GSL construction where all
+ /// argument origins should flow to the returned origin.
+ void handleFunctionCall(const Expr *Call, const FunctionDecl *FD,
+ ArrayRef<const Expr *> Args,
+ bool IsGslConstruction = false) {
+ // Ignore functions returning values with no origin.
+ if (!FD || !hasOrigin(Call))
+ return;
+ auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
+ const ParmVarDecl *PVD = nullptr;
+ if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
+ Method && Method->isInstance()) {
+ if (I == 0)
+ // For the 'this' argument, the attribute is on the method itself.
+ return implicitObjectParamIsLifetimeBound(Method);
+ if ((I - 1) < Method->getNumParams())
+ // For explicit arguments, find the corresponding parameter
+ // declaration.
+ PVD = Method->getParamDecl(I - 1);
+ } else if (I < FD->getNumParams())
+ // For free functions or static methods.
+ PVD = FD->getParamDecl(I);
+ return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
+ };
+ if (Args.empty())
+ return;
+ bool killedSrc = false;
+ for (unsigned I = 0; I < Args.size(); ++I)
+ if (IsGslConstruction || IsArgLifetimeBound(I)) {
+ if (!killedSrc) {
+ killedSrc = true;
+ killAndFlowOrigin(*Call, *Args[I]);
+ } else
+ flowOrigin(*Call, *Args[I]);
+ }
+ }
+
+ /// Creates a loan for the storage path of a given declaration reference.
+ /// This function should be called whenever a DeclRefExpr represents a borrow.
+ /// \param DRE The declaration reference expression that initiates the borrow.
+ /// \return The new Loan on success, nullptr otherwise.
+ const Loan *createLoan(const DeclRefExpr *DRE) {
+ if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) {
+ AccessPath Path(VD);
+ // The loan is created at the location of the DeclRefExpr.
+ return &FactMgr.getLoanMgr().addLoan(Path, DRE);
+ }
+ return nullptr;
+ }
+
+ template <typename Destination, typename Source>
+ void flowOrigin(const Destination &D, const Source &S) {
+ OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
+ OriginID SrcOID = FactMgr.getOriginMgr().get(S);
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ DestOID, SrcOID, /*KillDest=*/false));
+ }
+
+ template <typename Destination, typename Source>
+ void killAndFlowOrigin(const Destination &D, const Source &S) {
+ OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
+ OriginID SrcOID = FactMgr.getOriginMgr().get(S);
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID, /*KillDest=*/true));
+ }
+
+ /// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
+ /// If so, creates a `TestPointFact` and returns true.
+ bool handleTestPoint(const CXXFunctionalCastExpr *FCE) {
+ if (!FCE->getType()->isVoidType())
+ return false;
+
+ const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
+ if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) {
+ llvm::StringRef LiteralValue = SL->getString();
+ const std::string Prefix = "__lifetime_test_point_";
+
+ if (LiteralValue.starts_with(Prefix)) {
+ StringRef Annotation = LiteralValue.drop_front(Prefix.length());
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<TestPointFact>(Annotation));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr) {
+ if (!hasOrigin(LHSExpr))
+ return;
+ // Find the underlying variable declaration for the left-hand side.
+ if (const auto *DRE_LHS =
+ dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
+ markUseAsWrite(DRE_LHS);
+ if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) {
+ // Kill the old loans of the destination origin and flow the new loans
+ // from the source origin.
+ killAndFlowOrigin(*VD_LHS, *RHSExpr);
+ }
+ }
+ }
+
+ // A DeclRefExpr will be treated as a use of the referenced decl. It will be
+ // checked for use-after-free unless it is later marked as being written to
+ // (e.g. on the left-hand side of an assignment).
+ void handleUse(const DeclRefExpr *DRE) {
+ if (isPointerType(DRE->getType())) {
+ UseFact *UF = FactMgr.createFact<UseFact>(DRE);
+ CurrentBlockFacts.push_back(UF);
+ assert(!UseFacts.contains(DRE));
+ UseFacts[DRE] = UF;
+ }
+ }
+
+ void markUseAsWrite(const DeclRefExpr *DRE) {
+ if (!isPointerType(DRE->getType()))
+ return;
+ assert(UseFacts.contains(DRE));
+ UseFacts[DRE]->markAsWritten();
+ }
+
+ FactManager &FactMgr;
+ AnalysisDeclContext &AC;
+ llvm::SmallVector<Fact *> CurrentBlockFacts;
+ // To distinguish between reads and writes for use-after-free checks, this map
+ // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
+ // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use
+ // corresponding to the left-hand side is updated to be a "write", thereby
+ // exempting it from the check.
+ llvm::DenseMap<const DeclRefExpr *, UseFact *> UseFacts;
+};
+
+// ========================================================================= //
+// Generic Dataflow Analysis
+// ========================================================================= //
+
+enum class Direction { Forward, Backward };
+
+/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
+/// `Fact`. identified by a lifetime-related event (`Fact`).
+///
+/// A `ProgramPoint` has "after" semantics: it represents the location
+/// immediately after its corresponding `Fact`.
+using ProgramPoint = const Fact *;
+
+/// A generic, policy-based driver for dataflow analyses. It combines
+/// the dataflow runner and the transferer logic into a single class hierarchy.
+///
+/// The derived class is expected to provide:
+/// - A `Lattice` type.
+/// - `StringRef getAnalysisName() const`
+/// - `Lattice getInitialState();` The initial state of the analysis.
+/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths.
+/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single
+/// lifetime-relevant `Fact` transforms the lattice state. Only overloads
+/// for facts relevant to the analysis need to be implemented.
+///
+/// \tparam Derived The CRTP derived class that implements the specific
+/// analysis.
+/// \tparam LatticeType The dataflow lattice used by the analysis.
+/// \tparam Dir The direction of the analysis (Forward or Backward).
+/// TODO: Maybe use the dataflow framework! The framework might need changes
+/// to support the current comparison done at block-entry.
+template <typename Derived, typename LatticeType, Direction Dir>
+class DataflowAnalysis {
+public:
+ using Lattice = LatticeType;
+ using Base = DataflowAnalysis<Derived, Lattice, Dir>;
+
+private:
+ const CFG &Cfg;
+ AnalysisDeclContext &AC;
+
+ /// The dataflow state before a basic block is processed.
+ llvm::DenseMap<const CFGBlock *, Lattice> InStates;
+ /// The dataflow state after a basic block is processed.
+ llvm::DenseMap<const CFGBlock *, Lattice> OutStates;
+ /// The dataflow state at a Program Point.
+ /// In a forward analysis, this is the state after the Fact at that point has
+ /// been applied, while in a backward analysis, it is the state before.
+ llvm::DenseMap<ProgramPoint, Lattice> PerPointStates;
+
+ static constexpr bool isForward() { return Dir == Direction::Forward; }
+
+protected:
+ FactManager &AllFacts;
+
+ explicit DataflowAnalysis(const CFG &C, AnalysisDeclContext &AC,
+ FactManager &F)
+ : Cfg(C), AC(AC), AllFacts(F) {}
+
+public:
+ void run() {
+ Derived &D = static_cast<Derived &>(*this);
+ llvm::TimeTraceScope Time(D.getAnalysisName());
+
+ using Worklist =
+ std::conditional_t<Dir == Direction::Forward, ForwardDataflowWorklist,
+ BackwardDataflowWorklist>;
+ Worklist W(Cfg, AC);
+
+ const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit();
+ InStates[Start] = D.getInitialState();
+ W.enqueueBlock(Start);
+
+ while (const CFGBlock *B = W.dequeue()) {
+ Lattice StateIn = *getInState(B);
+ Lattice StateOut = transferBlock(B, StateIn);
+ OutStates[B] = StateOut;
+ for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) {
+ if (!AdjacentB)
+ continue;
+ std::optional<Lattice> OldInState = getInState(AdjacentB);
+ Lattice NewInState =
+ !OldInState ? StateOut : D.join(*OldInState, StateOut);
+ // Enqueue the adjacent block if its in-state has changed or if we have
+ // never seen it.
+ if (!OldInState || NewInState != *OldInState) {
+ InStates[AdjacentB] = NewInState;
+ W.enqueueBlock(AdjacentB);
+ }
+ }
+ }
+ }
+
+protected:
+ Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); }
+
+ std::optional<Lattice> getInState(const CFGBlock *B) const {
+ auto It = InStates.find(B);
+ if (It == InStates.end())
+ return std::nullopt;
+ return It->second;
+ }
+
+ Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); }
+
+ void dump() const {
+ const Derived *D = static_cast<const Derived *>(this);
+ llvm::dbgs() << "==========================================\n";
+ llvm::dbgs() << D->getAnalysisName() << " results:\n";
+ llvm::dbgs() << "==========================================\n";
+ const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry();
+ getOutState(&B).dump(llvm::dbgs());
+ }
+
+private:
+ /// Computes the state at one end of a block by applying all its facts
+ /// sequentially to a given state from the other end.
+ Lattice transferBlock(const CFGBlock *Block, Lattice State) {
+ auto Facts = AllFacts.getFacts(Block);
+ if constexpr (isForward()) {
+ for (const Fact *F : Facts) {
+ State = transferFact(State, F);
+ PerPointStates[F] = State;
+ }
+ } else {
+ for (const Fact *F : llvm::reverse(Facts)) {
+ // In backward analysis, capture the state before applying the fact.
+ PerPointStates[F] = State;
+ State = transferFact(State, F);
+ }
+ }
+ return State;
+ }
+
+ Lattice transferFact(Lattice In, const Fact *F) {
+ assert(F);
+ Derived *D = static_cast<Derived *>(this);
+ switch (F->getKind()) {
+ case Fact::Kind::Issue:
+ return D->transfer(In, *F->getAs<IssueFact>());
+ case Fact::Kind::Expire:
+ return D->transfer(In, *F->getAs<ExpireFact>());
+ case Fact::Kind::OriginFlow:
+ return D->transfer(In, *F->getAs<OriginFlowFact>());
+ 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>());
+ }
+ llvm_unreachable("Unknown fact kind");
+ }
+
+public:
+ Lattice transfer(Lattice In, const IssueFact &) { return In; }
+ Lattice transfer(Lattice In, const ExpireFact &) { return In; }
+ Lattice transfer(Lattice In, const OriginFlowFact &) { 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; }
+};
+
+namespace utils {
+
+/// Computes the union of two ImmutableSets.
+template <typename T>
+static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
+ llvm::ImmutableSet<T> B,
+ typename llvm::ImmutableSet<T>::Factory &F) {
+ if (A.getHeight() < B.getHeight())
+ std::swap(A, B);
+ for (const T &E : B)
+ A = F.add(A, E);
+ return A;
+}
+
+/// Describes the strategy for joining two `ImmutableMap` instances, primarily
+/// differing in how they handle keys that are unique to one of the maps.
+///
+/// A `Symmetric` join is universally correct, while an `Asymmetric` join
+/// serves as a performance optimization. The latter is applicable only when the
+/// join operation possesses a left identity element, allowing for a more
+/// efficient, one-sided merge.
+enum class JoinKind {
+ /// A symmetric join applies the `JoinValues` operation to keys unique to
+ /// either map, ensuring that values from both maps contribute to the result.
+ Symmetric,
+ /// An asymmetric join preserves keys unique to the first map as-is, while
+ /// applying the `JoinValues` operation only to keys unique to the second map.
+ Asymmetric,
+};
+
+/// 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
+// instead of the current AVL-tree-based ImmutableMap.
+template <typename K, typename V, typename Joiner>
+static llvm::ImmutableMap<K, V>
+join(const llvm::ImmutableMap<K, V> &A, const llvm::ImmutableMap<K, V> &B,
+ typename llvm::ImmutableMap<K, V>::Factory &F, Joiner JoinValues,
+ JoinKind Kind) {
+ if (A.getHeight() < B.getHeight())
+ return join(B, A, F, JoinValues, Kind);
+
+ // For each element in B, join it with the corresponding element in A
+ // (or with an empty value if it doesn't exist in A).
+ llvm::ImmutableMap<K, V> Res = A;
+ for (const auto &Entry : B) {
+ const K &Key = Entry.first;
+ const V &ValB = Entry.second;
+ Res = F.add(Res, Key, JoinValues(A.lookup(Key), &ValB));
+ }
+ if (Kind == JoinKind::Symmetric) {
+ for (const auto &Entry : A) {
+ const K &Key = Entry.first;
+ const V &ValA = Entry.second;
+ if (!B.contains(Key))
+ Res = F.add(Res, Key, JoinValues(&ValA, nullptr));
+ }
+ }
+ return Res;
+}
+} // namespace utils
+
+// ========================================================================= //
+// Loan Propagation Analysis
+// ========================================================================= //
+
+/// Represents the dataflow lattice for loan propagation.
+///
+/// This lattice tracks which loans each origin may hold at a given program
+/// point.The lattice has a finite height: An origin's loan set is bounded by
+/// the total number of loans in the function.
+/// TODO(opt): To reduce the lattice size, propagate origins of declarations,
+/// not expressions, because expressions are not visible across blocks.
+struct LoanPropagationLattice {
+ /// The map from an origin to the set of loans it contains.
+ OriginLoanMap Origins = OriginLoanMap(nullptr);
+
+ explicit LoanPropagationLattice(const OriginLoanMap &S) : Origins(S) {}
+ LoanPropagationLattice() = default;
+
+ bool operator==(const LoanPropagationLattice &Other) const {
+ return Origins == Other.Origins;
+ }
+ bool operator!=(const LoanPropagationLattice &Other) const {
+ return !(*this == Other);
+ }
+
+ void dump(llvm::raw_ostream &OS) const {
+ OS << "LoanPropagationLattice State:\n";
+ if (Origins.isEmpty())
+ OS << " <empty>\n";
+ for (const auto &Entry : Origins) {
+ if (Entry.second.isEmpty())
+ OS << " Origin " << Entry.first << " contains no loans\n";
+ for (const LoanID &LID : Entry.second)
+ OS << " Origin " << Entry.first << " contains Loan " << LID << "\n";
+ }
+ }
+};
+
+/// The analysis that tracks which loans belong to which origins.
+class LoanPropagationAnalysis
+ : public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice,
+ Direction::Forward> {
+ OriginLoanMap::Factory &OriginLoanMapFactory;
+ LoanSet::Factory &LoanSetFactory;
+
+public:
+ LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
+ OriginLoanMap::Factory &OriginLoanMapFactory,
+ LoanSet::Factory &LoanSetFactory)
+ : DataflowAnalysis(C, AC, F), OriginLoanMapFactory(OriginLoanMapFactory),
+ LoanSetFactory(LoanSetFactory) {}
+
+ using Base::transfer;
+
+ StringRef getAnalysisName() const { return "LoanPropagation"; }
+
+ Lattice getInitialState() { return Lattice{}; }
+
+ /// Merges two lattices by taking the union of loans for each origin.
+ // 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, OriginLoanMapFactory,
+ [&](const LoanSet *S1, const LoanSet *S2) {
+ assert((S1 || S2) && "unexpectedly merging 2 empty sets");
+ if (!S1)
+ return *S2;
+ if (!S2)
+ return *S1;
+ return utils::join(*S1, *S2, LoanSetFactory);
+ },
+ // Asymmetric join is a performance win. For origins present only on one
+ // branch, the loan set can be carried over as-is.
+ utils::JoinKind::Asymmetric);
+ return Lattice(JoinedOrigins);
+ }
+
+ /// A new loan is issued to the origin. Old loans are erased.
+ Lattice transfer(Lattice In, const IssueFact &F) {
+ OriginID OID = F.getOriginID();
+ LoanID LID = F.getLoanID();
+ return LoanPropagationLattice(OriginLoanMapFactory.add(
+ In.Origins, OID,
+ LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
+ }
+
+ /// A flow from source to destination. If `KillDest` is true, this replaces
+ /// the destination's loans with the source's. Otherwise, the source's loans
+ /// are merged into the destination's.
+ Lattice transfer(Lattice In, const OriginFlowFact &F) {
+ OriginID DestOID = F.getDestOriginID();
+ OriginID SrcOID = F.getSrcOriginID();
+
+ LoanSet DestLoans =
+ F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
+ LoanSet SrcLoans = getLoans(In, SrcOID);
+ LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
+
+ return LoanPropagationLattice(
+ OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans));
+ }
+
+ LoanSet getLoans(OriginID OID, ProgramPoint P) const {
+ return getLoans(getState(P), OID);
+ }
+
+private:
+ LoanSet getLoans(Lattice L, OriginID OID) const {
+ if (auto *Loans = L.Origins.lookup(OID))
+ return *Loans;
+ return LoanSetFactory.getEmptySet();
+ }
+};
+
+// ========================================================================= //
+// Live Origins Analysis
+// ========================================================================= //
+//
+// A backward dataflow analysis that determines which origins are "live" at each
+// program point. An origin is "live" at a program point if there's a potential
+// future use of the pointer it represents. Liveness is "generated" by a read of
+// origin's loan set (e.g., a `UseFact`) and is "killed" (i.e., it stops being
+// live) when its loan set is overwritten (e.g. a OriginFlow killing the
+// destination origin).
+//
+// This information is used for detecting use-after-free errors, as it allows us
+// to check if a live origin holds a loan to an object that has already expired.
+// ========================================================================= //
+
+/// Information about why an origin is live at a program point.
+struct LivenessInfo {
+ /// The use that makes the origin live. If liveness is propagated from
+ /// multiple uses along different paths, this will point to the use appearing
+ /// earlier in the translation unit.
+ /// This is 'null' when the origin is not live.
+ const UseFact *CausingUseFact;
+ /// The kind of liveness of the origin.
+ /// `Must`: The origin is live on all control-flow paths from the current
+ /// point to the function's exit (i.e. the current point is dominated by a set
+ /// of uses).
+ /// `Maybe`: indicates it is live on some but not all paths.
+ ///
+ /// This determines the diagnostic's confidence level.
+ /// `Must`-be-alive at expiration implies a definite use-after-free,
+ /// while `Maybe`-be-alive suggests a potential one on some paths.
+ LivenessKind Kind;
+
+ LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {}
+ LivenessInfo(const UseFact *UF, LivenessKind K)
+ : CausingUseFact(UF), Kind(K) {}
+
+ bool operator==(const LivenessInfo &Other) const {
+ return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind;
+ }
+ bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); }
+
+ void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
+ IDBuilder.AddPointer(CausingUseFact);
+ IDBuilder.Add(Kind);
+ }
+};
+
+using LivenessMap = llvm::ImmutableMap<OriginID, LivenessInfo>;
+
+/// The dataflow lattice for origin liveness analysis.
+/// It tracks which origins are live, why they're live (which UseFact),
+/// and the confidence level of that liveness.
+struct LivenessLattice {
+ LivenessMap LiveOrigins;
+
+ LivenessLattice() : LiveOrigins(nullptr) {};
+
+ explicit LivenessLattice(LivenessMap L) : LiveOrigins(L) {}
+
+ bool operator==(const LivenessLattice &Other) const {
+ return LiveOrigins == Other.LiveOrigins;
+ }
+
+ bool operator!=(const LivenessLattice &Other) const {
+ return !(*this == Other);
+ }
+
+ void dump(llvm::raw_ostream &OS, const OriginManager &OM) const {
+ if (LiveOrigins.isEmpty())
+ OS << " <empty>\n";
+ for (const auto &Entry : LiveOrigins) {
+ OriginID OID = Entry.first;
+ const LivenessInfo &Info = Entry.second;
+ OS << " ";
+ OM.dump(OID, OS);
+ OS << " is ";
+ switch (Info.Kind) {
+ case LivenessKind::Must:
+ OS << "definitely";
+ break;
+ case LivenessKind::Maybe:
+ OS << "maybe";
+ break;
+ case LivenessKind::Dead:
+ llvm_unreachable("liveness kind of live origins should not be dead.");
+ }
+ OS << " live at this point\n";
+ }
+ }
+};
+
+/// The analysis that tracks which origins are live, with granular information
+/// about the causing use fact and confidence level. This is a backward
+/// analysis.
+class LiveOriginAnalysis
+ : public DataflowAnalysis<LiveOriginAnalysis, LivenessLattice,
+ Direction::Backward> {
+ FactManager &FactMgr;
+ LivenessMap::Factory &Factory;
+
+public:
+ LiveOriginAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
+ LivenessMap::Factory &SF)
+ : DataflowAnalysis(C, AC, F), FactMgr(F), Factory(SF) {}
+ using DataflowAnalysis<LiveOriginAnalysis, Lattice,
+ Direction::Backward>::transfer;
+
+ StringRef getAnalysisName() const { return "LiveOrigins"; }
+
+ Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); }
+
+ /// Merges two lattices by combining liveness information.
+ /// When the same origin has different confidence levels, we take the lower
+ /// one.
+ Lattice join(Lattice L1, Lattice L2) const {
+ LivenessMap Merged = L1.LiveOrigins;
+ // Take the earliest UseFact to make the join hermetic and commutative.
+ auto CombineUseFact = [](const UseFact &A,
+ const UseFact &B) -> const UseFact * {
+ return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A
+ : &B;
+ };
+ auto CombineLivenessKind = [](LivenessKind K1,
+ LivenessKind K2) -> LivenessKind {
+ assert(K1 != LivenessKind::Dead && "LivenessKind should not be dead.");
+ assert(K2 != LivenessKind::Dead && "LivenessKind should not be dead.");
+ // Only return "Must" if both paths are "Must", otherwise Maybe.
+ if (K1 == LivenessKind::Must && K2 == LivenessKind::Must)
+ return LivenessKind::Must;
+ return LivenessKind::Maybe;
+ };
+ auto CombineLivenessInfo = [&](const LivenessInfo *L1,
+ const LivenessInfo *L2) -> LivenessInfo {
+ assert((L1 || L2) && "unexpectedly merging 2 empty sets");
+ if (!L1)
+ return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe);
+ if (!L2)
+ return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe);
+ return LivenessInfo(
+ CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact),
+ CombineLivenessKind(L1->Kind, L2->Kind));
+ };
+ return Lattice(utils::join(
+ L1.LiveOrigins, L2.LiveOrigins, Factory, CombineLivenessInfo,
+ // A symmetric join is required here. If an origin is live on one
+ // branch but not the other, its confidence must be demoted to `Maybe`.
+ utils::JoinKind::Symmetric));
+ }
+
+ /// A read operation makes the origin live with definite confidence, as it
+ /// dominates this program point. A write operation kills the liveness of
+ /// the origin since it overwrites the value.
+ Lattice transfer(Lattice In, const UseFact &UF) {
+ OriginID OID = UF.getUsedOrigin(FactMgr.getOriginMgr());
+ // Write kills liveness.
+ if (UF.isWritten())
+ return Lattice(Factory.remove(In.LiveOrigins, OID));
+ // Read makes origin live with definite confidence (dominates this point).
+ return Lattice(Factory.add(In.LiveOrigins, OID,
+ LivenessInfo(&UF, LivenessKind::Must)));
+ }
+
+ /// Issuing a new loan to an origin kills its liveness.
+ Lattice transfer(Lattice In, const IssueFact &IF) {
+ return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID()));
+ }
+
+ /// An OriginFlow kills the liveness of the destination origin if `KillDest`
+ /// is true. Otherwise, it propagates liveness from destination to source.
+ Lattice transfer(Lattice In, const OriginFlowFact &OF) {
+ if (!OF.getKillDest())
+ return In;
+ return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID()));
+ }
+
+ LivenessMap getLiveOrigins(ProgramPoint P) const {
+ return getState(P).LiveOrigins;
+ }
+
+ // Dump liveness values on all test points in the program.
+ void dump(llvm::raw_ostream &OS, const LifetimeSafetyAnalysis &LSA) const {
+ llvm::dbgs() << "==========================================\n";
+ llvm::dbgs() << getAnalysisName() << " results:\n";
+ llvm::dbgs() << "==========================================\n";
+ for (const auto &Entry : LSA.getTestPoints()) {
+ OS << "TestPoint: " << Entry.getKey() << "\n";
+ getState(Entry.getValue()).dump(OS, FactMgr.getOriginMgr());
+ }
+ }
+};
+
+// ========================================================================= //
+// 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;
+ LiveOriginAnalysis &LiveOrigins;
+ FactManager &FactMgr;
+ AnalysisDeclContext &ADC;
+ LifetimeSafetyReporter *Reporter;
+
+public:
+ LifetimeChecker(LoanPropagationAnalysis &LPA, LiveOriginAnalysis &LOA,
+ FactManager &FM, AnalysisDeclContext &ADC,
+ LifetimeSafetyReporter *Reporter)
+ : LoanPropagation(LPA), LiveOrigins(LOA), 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 *EF = F->getAs<ExpireFact>())
+ checkExpiry(EF);
+ issuePendingWarnings();
+ }
+
+ /// Checks for use-after-free errors when a loan expires.
+ ///
+ /// This method examines all live origins at the expiry point and determines
+ /// if any of them hold the expiring loan. If so, it creates a pending
+ /// warning with the appropriate confidence level based on the liveness
+ /// information. The confidence reflects whether the origin is definitely
+ /// or maybe live at this point.
+ ///
+ /// Note: This implementation considers only the confidence of origin
+ /// liveness. Future enhancements could also consider the confidence of loan
+ /// propagation (e.g., a loan may only be held on some execution paths).
+ void checkExpiry(const ExpireFact *EF) {
+ LoanID ExpiredLoan = EF->getLoanID();
+ LivenessMap Origins = LiveOrigins.getLiveOrigins(EF);
+ Confidence CurConfidence = Confidence::None;
+ const UseFact *BadUse = nullptr;
+ for (auto &[OID, LiveInfo] : Origins) {
+ LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
+ if (!HeldLoans.contains(ExpiredLoan))
+ continue;
+ // Loan is defaulted.
+ Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
+ if (CurConfidence < NewConfidence) {
+ CurConfidence = NewConfidence;
+ BadUse = LiveInfo.CausingUseFact;
+ }
+ }
+ if (!BadUse)
+ return;
+ // We have a use-after-free.
+ Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
+ if (LastConf >= CurConfidence)
+ return;
+ FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
+ /*UseExpr=*/BadUse->getUseExpr(),
+ /*ConfidenceLevel=*/CurConfidence};
+ }
+
+ static Confidence livenessKindToConfidence(LivenessKind K) {
+ switch (K) {
+ case LivenessKind::Must:
+ return Confidence::Definite;
+ case LivenessKind::Maybe:
+ return Confidence::Maybe;
+ case LivenessKind::Dead:
+ return Confidence::None;
+ }
+ llvm_unreachable("unknown liveness kind");
+ }
+
+ 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
+// ========================================================================= //
+
+/// An object to hold the factories for immutable collections, ensuring
+/// that all created states share the same underlying memory management.
+struct LifetimeFactory {
+ llvm::BumpPtrAllocator Allocator;
+ OriginLoanMap::Factory OriginMapFactory{Allocator, /*canonicalize=*/false};
+ LoanSet::Factory LoanSetFactory{Allocator, /*canonicalize=*/false};
+ LivenessMap::Factory LivenessMapFactory{Allocator, /*canonicalize=*/false};
+};
+
+// We need this here for unique_ptr with forward declared class.
+LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
+
+LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
+ LifetimeSafetyReporter *Reporter)
+ : AC(AC), Reporter(Reporter), Factory(std::make_unique<LifetimeFactory>()),
+ FactMgr(std::make_unique<FactManager>()) {}
+
+void LifetimeSafetyAnalysis::run() {
+ llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
+
+ const CFG &Cfg = *AC.getCFG();
+ DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
+ /*ShowColors=*/true));
+
+ FactGenerator FactGen(*FactMgr, AC);
+ FactGen.run();
+ DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC));
+
+ /// TODO(opt): Consider optimizing individual blocks before running the
+ /// dataflow analysis.
+ /// 1. Expression Origins: These are assigned once and read at most once,
+ /// forming simple chains. These chains can be compressed into a single
+ /// assignment.
+ /// 2. Block-Local Loans: Origins of expressions are never read by other
+ /// 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->OriginMapFactory, Factory->LoanSetFactory);
+ LoanPropagation->run();
+
+ LiveOrigins = std::make_unique<LiveOriginAnalysis>(
+ Cfg, AC, *FactMgr, Factory->LivenessMapFactory);
+ LiveOrigins->run();
+ DEBUG_WITH_TYPE("LiveOrigins", LiveOrigins->dump(llvm::dbgs(), *this));
+
+ LifetimeChecker Checker(*LoanPropagation, *LiveOrigins, *FactMgr, AC,
+ Reporter);
+ Checker.run();
+}
+
+LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
+ ProgramPoint PP) const {
+ assert(LoanPropagation && "Analysis has not been run.");
+ return LoanPropagation->getLoans(OID, PP);
+}
+
+std::optional<OriginID>
+LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const {
+ assert(FactMgr && "FactManager not initialized");
+ // This assumes the OriginManager's `get` can find an existing origin.
+ // We might need a `find` method on OriginManager to avoid `getOrCreate` logic
+ // in a const-query context if that becomes an issue.
+ return FactMgr->getOriginMgr().get(*D);
+}
+
+std::vector<LoanID>
+LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const {
+ assert(FactMgr && "FactManager not initialized");
+ std::vector<LoanID> Result;
+ for (const Loan &L : FactMgr->getLoanMgr().getLoans())
+ if (L.Path.D == VD)
+ Result.push_back(L.ID);
+ return Result;
+}
+
+std::vector<std::pair<OriginID, LivenessKind>>
+LifetimeSafetyAnalysis::getLiveOriginsAtPoint(ProgramPoint PP) const {
+ assert(LiveOrigins && "LiveOriginAnalysis has not been run.");
+ std::vector<std::pair<OriginID, LivenessKind>> Result;
+ for (auto &[OID, Info] : LiveOrigins->getLiveOrigins(PP))
+ Result.push_back({OID, Info.Kind});
+ return Result;
+}
+
+llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const {
+ assert(FactMgr && "FactManager not initialized");
+ llvm::StringMap<ProgramPoint> AnnotationToPointMap;
+ for (const CFGBlock *Block : *AC.getCFG()) {
+ for (const Fact *F : FactMgr->getFacts(Block)) {
+ if (const auto *TPF = F->getAs<TestPointFact>()) {
+ StringRef PointName = TPF->getAnnotation();
+ assert(AnnotationToPointMap.find(PointName) ==
+ AnnotationToPointMap.end() &&
+ "more than one test points with the same name");
+ AnnotationToPointMap[PointName] = F;
+ }
+ }
+ }
+ return AnnotationToPointMap;
+}
+} // namespace internal
+
+void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
+ LifetimeSafetyReporter *Reporter) {
+ internal::LifetimeSafetyAnalysis Analysis(AC, Reporter);
+ Analysis.run();
+}
+} // namespace clang::lifetimes
diff --git a/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt b/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt
deleted file mode 100644
index 8584292..0000000
--- a/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-add_clang_library(clangAnalysisLifetimeSafety
- Checker.cpp
- Facts.cpp
- FactsGenerator.cpp
- LifetimeAnnotations.cpp
- LifetimeSafety.cpp
- LiveOrigins.cpp
- Loans.cpp
- LoanPropagation.cpp
- Origins.cpp
- )
-
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
deleted file mode 100644
index c443c3a..0000000
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-//===- Checker.cpp - C++ Lifetime Safety Checker ----------------*- 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 implements the LifetimeChecker, which detects use-after-free
-// errors by checking if live origins hold loans that have expired.
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h"
-#include "clang/AST/Expr.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
-#include "clang/Analysis/Analyses/PostOrderCFGView.h"
-#include "clang/Analysis/AnalysisDeclContext.h"
-#include "clang/Basic/SourceLocation.h"
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/Support/ErrorHandling.h"
-#include "llvm/Support/TimeProfiler.h"
-
-namespace clang::lifetimes::internal {
-
-static Confidence livenessKindToConfidence(LivenessKind K) {
- switch (K) {
- case LivenessKind::Must:
- return Confidence::Definite;
- case LivenessKind::Maybe:
- return Confidence::Maybe;
- case LivenessKind::Dead:
- return Confidence::None;
- }
- llvm_unreachable("unknown liveness kind");
-}
-
-namespace {
-
-/// 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;
- const LoanPropagationAnalysis &LoanPropagation;
- const LiveOriginsAnalysis &LiveOrigins;
- const FactManager &FactMgr;
- LifetimeSafetyReporter *Reporter;
-
-public:
- LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
- const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM,
- AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
- : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
- Reporter(Reporter) {
- for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
- for (const Fact *F : FactMgr.getFacts(B))
- if (const auto *EF = F->getAs<ExpireFact>())
- checkExpiry(EF);
- issuePendingWarnings();
- }
-
- /// Checks for use-after-free errors when a loan expires.
- ///
- /// This method examines all live origins at the expiry point and determines
- /// if any of them hold the expiring loan. If so, it creates a pending
- /// warning with the appropriate confidence level based on the liveness
- /// information. The confidence reflects whether the origin is definitely
- /// or maybe live at this point.
- ///
- /// Note: This implementation considers only the confidence of origin
- /// liveness. Future enhancements could also consider the confidence of loan
- /// propagation (e.g., a loan may only be held on some execution paths).
- void checkExpiry(const ExpireFact *EF) {
- LoanID ExpiredLoan = EF->getLoanID();
- LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
- Confidence CurConfidence = Confidence::None;
- const UseFact *BadUse = nullptr;
- for (auto &[OID, LiveInfo] : Origins) {
- LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
- if (!HeldLoans.contains(ExpiredLoan))
- continue;
- // Loan is defaulted.
- Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
- if (CurConfidence < NewConfidence) {
- CurConfidence = NewConfidence;
- BadUse = LiveInfo.CausingUseFact;
- }
- }
- if (!BadUse)
- return;
- // We have a use-after-free.
- Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
- if (LastConf >= CurConfidence)
- return;
- FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
- /*UseExpr=*/BadUse->getUseExpr(),
- /*ConfidenceLevel=*/CurConfidence};
- }
-
- 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);
- }
- }
-};
-} // namespace
-
-void runLifetimeChecker(const LoanPropagationAnalysis &LP,
- const LiveOriginsAnalysis &LO,
- const FactManager &FactMgr, AnalysisDeclContext &ADC,
- LifetimeSafetyReporter *Reporter) {
- llvm::TimeTraceScope TimeProfile("LifetimeChecker");
- LifetimeChecker Checker(LP, LO, FactMgr, ADC, Reporter);
-}
-
-} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
deleted file mode 100644
index 2f7bcb6..0000000
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ /dev/null
@@ -1,188 +0,0 @@
-//===- Dataflow.h - Generic Dataflow Analysis Framework --------*- 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 a generic, policy-based driver for dataflow analyses.
-// It provides a flexible framework that combines the dataflow runner and
-// transfer functions, allowing derived classes to implement specific analyses
-// by defining their lattice, join, and transfer functions.
-//
-//===----------------------------------------------------------------------===//
-#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
-#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
-
-#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
-#include "clang/Analysis/AnalysisDeclContext.h"
-#include "clang/Analysis/CFG.h"
-#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
-#include "llvm/Support/Debug.h"
-#include "llvm/Support/ErrorHandling.h"
-#include "llvm/Support/TimeProfiler.h"
-#include <optional>
-
-namespace clang::lifetimes::internal {
-
-enum class Direction { Forward, Backward };
-
-/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
-/// `Fact`. identified by a lifetime-related event (`Fact`).
-///
-/// A `ProgramPoint` has "after" semantics: it represents the location
-/// immediately after its corresponding `Fact`.
-using ProgramPoint = const Fact *;
-
-/// A generic, policy-based driver for dataflow analyses. It combines
-/// the dataflow runner and the transferer logic into a single class hierarchy.
-///
-/// The derived class is expected to provide:
-/// - A `Lattice` type.
-/// - `StringRef getAnalysisName() const`
-/// - `Lattice getInitialState();` The initial state of the analysis.
-/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths.
-/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single
-/// lifetime-relevant `Fact` transforms the lattice state. Only overloads
-/// for facts relevant to the analysis need to be implemented.
-///
-/// \tparam Derived The CRTP derived class that implements the specific
-/// analysis.
-/// \tparam LatticeType The dataflow lattice used by the analysis.
-/// \tparam Dir The direction of the analysis (Forward or Backward).
-/// TODO: Maybe use the dataflow framework! The framework might need changes
-/// to support the current comparison done at block-entry.
-template <typename Derived, typename LatticeType, Direction Dir>
-class DataflowAnalysis {
-public:
- using Lattice = LatticeType;
- using Base = DataflowAnalysis<Derived, Lattice, Dir>;
-
-private:
- const CFG &Cfg;
- AnalysisDeclContext &AC;
-
- /// The dataflow state before a basic block is processed.
- llvm::DenseMap<const CFGBlock *, Lattice> InStates;
- /// The dataflow state after a basic block is processed.
- llvm::DenseMap<const CFGBlock *, Lattice> OutStates;
- /// The dataflow state at a Program Point.
- /// In a forward analysis, this is the state after the Fact at that point has
- /// been applied, while in a backward analysis, it is the state before.
- llvm::DenseMap<ProgramPoint, Lattice> PerPointStates;
-
- static constexpr bool isForward() { return Dir == Direction::Forward; }
-
-protected:
- FactManager &FactMgr;
-
- explicit DataflowAnalysis(const CFG &Cfg, AnalysisDeclContext &AC,
- FactManager &FactMgr)
- : Cfg(Cfg), AC(AC), FactMgr(FactMgr) {}
-
-public:
- void run() {
- Derived &D = static_cast<Derived &>(*this);
- llvm::TimeTraceScope Time(D.getAnalysisName());
-
- using Worklist =
- std::conditional_t<Dir == Direction::Forward, ForwardDataflowWorklist,
- BackwardDataflowWorklist>;
- Worklist W(Cfg, AC);
-
- const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit();
- InStates[Start] = D.getInitialState();
- W.enqueueBlock(Start);
-
- while (const CFGBlock *B = W.dequeue()) {
- Lattice StateIn = *getInState(B);
- Lattice StateOut = transferBlock(B, StateIn);
- OutStates[B] = StateOut;
- for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) {
- if (!AdjacentB)
- continue;
- std::optional<Lattice> OldInState = getInState(AdjacentB);
- Lattice NewInState =
- !OldInState ? StateOut : D.join(*OldInState, StateOut);
- // Enqueue the adjacent block if its in-state has changed or if we have
- // never seen it.
- if (!OldInState || NewInState != *OldInState) {
- InStates[AdjacentB] = NewInState;
- W.enqueueBlock(AdjacentB);
- }
- }
- }
- }
-
-protected:
- Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); }
-
- std::optional<Lattice> getInState(const CFGBlock *B) const {
- auto It = InStates.find(B);
- if (It == InStates.end())
- return std::nullopt;
- return It->second;
- }
-
- Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); }
-
- void dump() const {
- const Derived *D = static_cast<const Derived *>(this);
- llvm::dbgs() << "==========================================\n";
- llvm::dbgs() << D->getAnalysisName() << " results:\n";
- llvm::dbgs() << "==========================================\n";
- const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry();
- getOutState(&B).dump(llvm::dbgs());
- }
-
-private:
- /// Computes the state at one end of a block by applying all its facts
- /// sequentially to a given state from the other end.
- Lattice transferBlock(const CFGBlock *Block, Lattice State) {
- auto Facts = FactMgr.getFacts(Block);
- if constexpr (isForward()) {
- for (const Fact *F : Facts) {
- State = transferFact(State, F);
- PerPointStates[F] = State;
- }
- } else {
- for (const Fact *F : llvm::reverse(Facts)) {
- // In backward analysis, capture the state before applying the fact.
- PerPointStates[F] = State;
- State = transferFact(State, F);
- }
- }
- return State;
- }
-
- Lattice transferFact(Lattice In, const Fact *F) {
- assert(F);
- Derived *D = static_cast<Derived *>(this);
- switch (F->getKind()) {
- case Fact::Kind::Issue:
- return D->transfer(In, *F->getAs<IssueFact>());
- case Fact::Kind::Expire:
- return D->transfer(In, *F->getAs<ExpireFact>());
- case Fact::Kind::OriginFlow:
- return D->transfer(In, *F->getAs<OriginFlowFact>());
- 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>());
- }
- llvm_unreachable("Unknown fact kind");
- }
-
-public:
- Lattice transfer(Lattice In, const IssueFact &) { return In; }
- Lattice transfer(Lattice In, const ExpireFact &) { return In; }
- Lattice transfer(Lattice In, const OriginFlowFact &) { 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; }
-};
-} // namespace clang::lifetimes::internal
-#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
deleted file mode 100644
index 1aea64f..0000000
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-//===- Facts.cpp - Lifetime Analysis Facts Implementation -------*- 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/Analyses/LifetimeSafety/Facts.h"
-#include "clang/AST/Decl.h"
-#include "clang/Analysis/Analyses/PostOrderCFGView.h"
-
-namespace clang::lifetimes::internal {
-
-void Fact::dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &) const {
- OS << "Fact (Kind: " << static_cast<int>(K) << ")\n";
-}
-
-void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
- const OriginManager &OM) const {
- OS << "Issue (";
- LM.getLoan(getLoanID()).dump(OS);
- OS << ", ToOrigin: ";
- OM.dump(getOriginID(), OS);
- OS << ")\n";
-}
-
-void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
- const OriginManager &) const {
- OS << "Expire (";
- LM.getLoan(getLoanID()).dump(OS);
- OS << ")\n";
-}
-
-void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &OM) const {
- OS << "OriginFlow (Dest: ";
- OM.dump(getDestOriginID(), OS);
- OS << ", Src: ";
- OM.dump(getSrcOriginID(), OS);
- OS << (getKillDest() ? "" : ", Merge");
- OS << ")\n";
-}
-
-void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &OM) const {
- OS << "ReturnOfOrigin (";
- OM.dump(getReturnedOriginID(), OS);
- OS << ")\n";
-}
-
-void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &OM) const {
- OS << "Use (";
- OM.dump(getUsedOrigin(OM), OS);
- OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
-}
-
-void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &) const {
- OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
-}
-
-llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const {
- llvm::StringMap<ProgramPoint> AnnotationToPointMap;
- for (const CFGBlock *Block : BlockToFactsMap.keys()) {
- for (const Fact *F : getFacts(Block)) {
- if (const auto *TPF = F->getAs<TestPointFact>()) {
- StringRef PointName = TPF->getAnnotation();
- assert(AnnotationToPointMap.find(PointName) ==
- AnnotationToPointMap.end() &&
- "more than one test points with the same name");
- AnnotationToPointMap[PointName] = F;
- }
- }
- }
- return AnnotationToPointMap;
-}
-
-void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
- llvm::dbgs() << "==========================================\n";
- llvm::dbgs() << " Lifetime Analysis Facts:\n";
- llvm::dbgs() << "==========================================\n";
- if (const Decl *D = AC.getDecl())
- if (const auto *ND = dyn_cast<NamedDecl>(D))
- llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n";
- // Print blocks in the order as they appear in code for a stable ordering.
- for (const CFGBlock *B : *AC.getAnalysis<PostOrderCFGView>()) {
- llvm::dbgs() << " Block B" << B->getBlockID() << ":\n";
- auto It = BlockToFactsMap.find(B);
- if (It != BlockToFactsMap.end()) {
- for (const Fact *F : It->second) {
- llvm::dbgs() << " ";
- F->dump(llvm::dbgs(), LoanMgr, OriginMgr);
- }
- }
- llvm::dbgs() << " End of Block\n";
- }
-}
-
-} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
deleted file mode 100644
index 485308f..0000000
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ /dev/null
@@ -1,348 +0,0 @@
-//===- FactsGenerator.cpp - Lifetime Facts Generation -----------*- 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/Analyses/LifetimeSafety/FactsGenerator.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
-#include "clang/Analysis/Analyses/PostOrderCFGView.h"
-#include "llvm/Support/TimeProfiler.h"
-
-namespace clang::lifetimes::internal {
-
-static bool isGslPointerType(QualType QT) {
- if (const auto *RD = QT->getAsCXXRecordDecl()) {
- // We need to check the template definition for specializations.
- if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
- return CTSD->getSpecializedTemplate()
- ->getTemplatedDecl()
- ->hasAttr<PointerAttr>();
- return RD->hasAttr<PointerAttr>();
- }
- return false;
-}
-
-static bool isPointerType(QualType QT) {
- return QT->isPointerOrReferenceType() || isGslPointerType(QT);
-}
-// Check if a type has an origin.
-static bool hasOrigin(const Expr *E) {
- return E->isGLValue() || isPointerType(E->getType());
-}
-
-static bool hasOrigin(const VarDecl *VD) {
- return isPointerType(VD->getType());
-}
-
-/// Creates a loan for the storage path of a given declaration reference.
-/// This function should be called whenever a DeclRefExpr represents a borrow.
-/// \param DRE The declaration reference expression that initiates the borrow.
-/// \return The new Loan on success, nullptr otherwise.
-static const Loan *createLoan(FactManager &FactMgr, const DeclRefExpr *DRE) {
- if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) {
- AccessPath Path(VD);
- // The loan is created at the location of the DeclRefExpr.
- return &FactMgr.getLoanMgr().addLoan(Path, DRE);
- }
- return nullptr;
-}
-
-void FactsGenerator::run() {
- llvm::TimeTraceScope TimeProfile("FactGenerator");
- // Iterate through the CFG blocks in reverse post-order to ensure that
- // initializations and destructions are processed in the correct sequence.
- for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
- CurrentBlockFacts.clear();
- for (unsigned I = 0; I < Block->size(); ++I) {
- const CFGElement &Element = Block->Elements[I];
- if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
- Visit(CS->getStmt());
- else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
- Element.getAs<CFGAutomaticObjDtor>())
- handleDestructor(*DtorOpt);
- }
- FactMgr.addBlockFacts(Block, CurrentBlockFacts);
- }
-}
-
-void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) {
- for (const Decl *D : DS->decls())
- if (const auto *VD = dyn_cast<VarDecl>(D))
- if (hasOrigin(VD))
- if (const Expr *InitExpr = VD->getInit())
- killAndFlowOrigin(*VD, *InitExpr);
-}
-
-void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
- handleUse(DRE);
- // For non-pointer/non-view types, a reference to the variable's storage
- // is a borrow. We create a loan for it.
- // For pointer/view types, we stick to the existing model for now and do
- // not create an extra origin for the l-value expression itself.
-
- // TODO: A single origin for a `DeclRefExpr` for a pointer or view type is
- // not sufficient to model the different levels of indirection. The current
- // single-origin model cannot distinguish between a loan to the variable's
- // storage and a loan to what it points to. A multi-origin model would be
- // required for this.
- if (!isPointerType(DRE->getType())) {
- if (const Loan *L = createLoan(FactMgr, DRE)) {
- OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE);
- CurrentBlockFacts.push_back(
- FactMgr.createFact<IssueFact>(L->ID, ExprOID));
- }
- }
-}
-
-void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
- if (isGslPointerType(CCE->getType())) {
- handleGSLPointerConstruction(CCE);
- return;
- }
-}
-
-void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
- // Specifically for conversion operators,
- // like `std::string_view p = std::string{};`
- if (isGslPointerType(MCE->getType()) &&
- isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
- // The argument is the implicit object itself.
- handleFunctionCall(MCE, MCE->getMethodDecl(),
- {MCE->getImplicitObjectArgument()},
- /*IsGslConstruction=*/true);
- }
- if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
- // Construct the argument list, with the implicit 'this' object as the
- // first argument.
- llvm::SmallVector<const Expr *, 4> Args;
- Args.push_back(MCE->getImplicitObjectArgument());
- Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs());
-
- handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false);
- }
-}
-
-void FactsGenerator::VisitCallExpr(const CallExpr *CE) {
- handleFunctionCall(CE, CE->getDirectCallee(),
- {CE->getArgs(), CE->getNumArgs()});
-}
-
-void FactsGenerator::VisitCXXNullPtrLiteralExpr(
- const CXXNullPtrLiteralExpr *N) {
- /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
- /// pointers can use the same type of loan.
- FactMgr.getOriginMgr().getOrCreate(*N);
-}
-
-void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
- if (!hasOrigin(ICE))
- return;
- // An ImplicitCastExpr node itself gets an origin, which flows from the
- // origin of its sub-expression (after stripping its own parens/casts).
- killAndFlowOrigin(*ICE, *ICE->getSubExpr());
-}
-
-void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) {
- if (UO->getOpcode() == UO_AddrOf) {
- const Expr *SubExpr = UO->getSubExpr();
- // Taking address of a pointer-type expression is not yet supported and
- // will be supported in multi-origin model.
- if (isPointerType(SubExpr->getType()))
- return;
- // The origin of an address-of expression (e.g., &x) is the origin of
- // its sub-expression (x). This fact will cause the dataflow analysis
- // to propagate any loans held by the sub-expression's origin to the
- // origin of this UnaryOperator expression.
- killAndFlowOrigin(*UO, *SubExpr);
- }
-}
-
-void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
- if (const Expr *RetExpr = RS->getRetValue()) {
- if (hasOrigin(RetExpr)) {
- OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
- CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID));
- }
- }
-}
-
-void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
- if (BO->isAssignmentOp())
- handleAssignment(BO->getLHS(), BO->getRHS());
-}
-
-void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
- // Assignment operators have special "kill-then-propagate" semantics
- // and are handled separately.
- if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) {
- handleAssignment(OCE->getArg(0), OCE->getArg(1));
- return;
- }
- handleFunctionCall(OCE, OCE->getDirectCallee(),
- {OCE->getArgs(), OCE->getNumArgs()},
- /*IsGslConstruction=*/false);
-}
-
-void FactsGenerator::VisitCXXFunctionalCastExpr(
- const CXXFunctionalCastExpr *FCE) {
- // Check if this is a test point marker. If so, we are done with this
- // expression.
- if (handleTestPoint(FCE))
- return;
- if (isGslPointerType(FCE->getType()))
- killAndFlowOrigin(*FCE, *FCE->getSubExpr());
-}
-
-void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
- if (!hasOrigin(ILE))
- return;
- // For list initialization with a single element, like `View{...}`, the
- // origin of the list itself is the origin of its single element.
- if (ILE->getNumInits() == 1)
- killAndFlowOrigin(*ILE, *ILE->getInit(0));
-}
-
-void FactsGenerator::VisitMaterializeTemporaryExpr(
- const MaterializeTemporaryExpr *MTE) {
- if (!hasOrigin(MTE))
- return;
- // A temporary object's origin is the same as the origin of the
- // expression that initializes it.
- killAndFlowOrigin(*MTE, *MTE->getSubExpr());
-}
-
-void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
- /// TODO: Also handle trivial destructors (e.g., for `int`
- /// variables) which will never have a CFGAutomaticObjDtor node.
- /// TODO: Handle loans to temporaries.
- /// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the
- /// lifetime ends.
- const VarDecl *DestructedVD = DtorOpt.getVarDecl();
- if (!DestructedVD)
- return;
- // Iterate through all loans to see if any expire.
- /// TODO(opt): Do better than a linear search to find loans associated with
- /// 'DestructedVD'.
- for (const Loan &L : FactMgr.getLoanMgr().getLoans()) {
- const AccessPath &LoanPath = L.Path;
- // 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, DtorOpt.getTriggerStmt()->getEndLoc()));
- }
-}
-
-void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
- assert(isGslPointerType(CCE->getType()));
- if (CCE->getNumArgs() != 1)
- return;
- if (hasOrigin(CCE->getArg(0)))
- killAndFlowOrigin(*CCE, *CCE->getArg(0));
- else
- // This could be a new borrow.
- handleFunctionCall(CCE, CCE->getConstructor(),
- {CCE->getArgs(), CCE->getNumArgs()},
- /*IsGslConstruction=*/true);
-}
-
-/// Checks if a call-like expression creates a borrow by passing a value to a
-/// reference parameter, creating an IssueFact if it does.
-/// \param IsGslConstruction True if this is a GSL construction where all
-/// argument origins should flow to the returned origin.
-void FactsGenerator::handleFunctionCall(const Expr *Call,
- const FunctionDecl *FD,
- ArrayRef<const Expr *> Args,
- bool IsGslConstruction) {
- // Ignore functions returning values with no origin.
- if (!FD || !hasOrigin(Call))
- return;
- auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
- const ParmVarDecl *PVD = nullptr;
- if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
- Method && Method->isInstance()) {
- if (I == 0)
- // For the 'this' argument, the attribute is on the method itself.
- return implicitObjectParamIsLifetimeBound(Method);
- if ((I - 1) < Method->getNumParams())
- // For explicit arguments, find the corresponding parameter
- // declaration.
- PVD = Method->getParamDecl(I - 1);
- } else if (I < FD->getNumParams())
- // For free functions or static methods.
- PVD = FD->getParamDecl(I);
- return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
- };
- if (Args.empty())
- return;
- bool killedSrc = false;
- for (unsigned I = 0; I < Args.size(); ++I)
- if (IsGslConstruction || IsArgLifetimeBound(I)) {
- if (!killedSrc) {
- killedSrc = true;
- killAndFlowOrigin(*Call, *Args[I]);
- } else
- flowOrigin(*Call, *Args[I]);
- }
-}
-
-/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
-/// If so, creates a `TestPointFact` and returns true.
-bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) {
- if (!FCE->getType()->isVoidType())
- return false;
-
- const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
- if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) {
- llvm::StringRef LiteralValue = SL->getString();
- const std::string Prefix = "__lifetime_test_point_";
-
- if (LiteralValue.starts_with(Prefix)) {
- StringRef Annotation = LiteralValue.drop_front(Prefix.length());
- CurrentBlockFacts.push_back(
- FactMgr.createFact<TestPointFact>(Annotation));
- return true;
- }
- }
- return false;
-}
-
-void FactsGenerator::handleAssignment(const Expr *LHSExpr,
- const Expr *RHSExpr) {
- if (!hasOrigin(LHSExpr))
- return;
- // Find the underlying variable declaration for the left-hand side.
- if (const auto *DRE_LHS =
- dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
- markUseAsWrite(DRE_LHS);
- if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) {
- // Kill the old loans of the destination origin and flow the new loans
- // from the source origin.
- killAndFlowOrigin(*VD_LHS, *RHSExpr);
- }
- }
-}
-
-// A DeclRefExpr will be treated as a use of the referenced decl. It will be
-// checked for use-after-free unless it is later marked as being written to
-// (e.g. on the left-hand side of an assignment).
-void FactsGenerator::handleUse(const DeclRefExpr *DRE) {
- if (isPointerType(DRE->getType())) {
- UseFact *UF = FactMgr.createFact<UseFact>(DRE);
- CurrentBlockFacts.push_back(UF);
- assert(!UseFacts.contains(DRE));
- UseFacts[DRE] = UF;
- }
-}
-
-void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
- if (!isPointerType(DRE->getType()))
- return;
- assert(UseFacts.contains(DRE));
- UseFacts[DRE]->markAsWritten();
-}
-
-} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
deleted file mode 100644
index 00c7ed90..0000000
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-//===- LifetimeSafety.cpp - C++ Lifetime Safety Analysis -*--------- 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 implements the main LifetimeSafetyAnalysis class, which coordinates
-// the various components (fact generation, loan propagation, live origins
-// analysis, and checking) to detect lifetime safety violations in C++ code.
-//
-//===----------------------------------------------------------------------===//
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
-#include "clang/AST/Decl.h"
-#include "clang/AST/Expr.h"
-#include "clang/AST/Type.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
-#include "clang/Analysis/AnalysisDeclContext.h"
-#include "clang/Analysis/CFG.h"
-#include "llvm/ADT/FoldingSet.h"
-#include "llvm/Support/Debug.h"
-#include "llvm/Support/ErrorHandling.h"
-#include "llvm/Support/TimeProfiler.h"
-#include <memory>
-
-namespace clang::lifetimes {
-namespace internal {
-
-LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
- LifetimeSafetyReporter *Reporter)
- : AC(AC), Reporter(Reporter) {}
-
-void LifetimeSafetyAnalysis::run() {
- llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
-
- const CFG &Cfg = *AC.getCFG();
- DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
- /*ShowColors=*/true));
-
- FactsGenerator FactGen(FactMgr, AC);
- FactGen.run();
- DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
-
- /// TODO(opt): Consider optimizing individual blocks before running the
- /// dataflow analysis.
- /// 1. Expression Origins: These are assigned once and read at most once,
- /// forming simple chains. These chains can be compressed into a single
- /// assignment.
- /// 2. Block-Local Loans: Origins of expressions are never read by other
- /// 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.OriginMapFactory, Factory.LoanSetFactory);
-
- LiveOrigins = std::make_unique<LiveOriginsAnalysis>(
- Cfg, AC, FactMgr, Factory.LivenessMapFactory);
- DEBUG_WITH_TYPE("LiveOrigins",
- LiveOrigins->dump(llvm::dbgs(), FactMgr.getTestPoints()));
-
- runLifetimeChecker(*LoanPropagation, *LiveOrigins, FactMgr, AC, Reporter);
-}
-} // namespace internal
-
-void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
- LifetimeSafetyReporter *Reporter) {
- internal::LifetimeSafetyAnalysis Analysis(AC, Reporter);
- Analysis.run();
-}
-} // namespace clang::lifetimes
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
deleted file mode 100644
index cddb3f3c..0000000
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ /dev/null
@@ -1,180 +0,0 @@
-//===- LiveOrigins.cpp - Live Origins Analysis -----------------*- 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/Analyses/LifetimeSafety/LiveOrigins.h"
-#include "Dataflow.h"
-#include "llvm/Support/ErrorHandling.h"
-
-namespace clang::lifetimes::internal {
-namespace {
-
-/// The dataflow lattice for origin liveness analysis.
-/// It tracks which origins are live, why they're live (which UseFact),
-/// and the confidence level of that liveness.
-struct Lattice {
- LivenessMap LiveOrigins;
-
- Lattice() : LiveOrigins(nullptr) {};
-
- explicit Lattice(LivenessMap L) : LiveOrigins(L) {}
-
- bool operator==(const Lattice &Other) const {
- return LiveOrigins == Other.LiveOrigins;
- }
-
- bool operator!=(const Lattice &Other) const { return !(*this == Other); }
-
- void dump(llvm::raw_ostream &OS, const OriginManager &OM) const {
- if (LiveOrigins.isEmpty())
- OS << " <empty>\n";
- for (const auto &Entry : LiveOrigins) {
- OriginID OID = Entry.first;
- const LivenessInfo &Info = Entry.second;
- OS << " ";
- OM.dump(OID, OS);
- OS << " is ";
- switch (Info.Kind) {
- case LivenessKind::Must:
- OS << "definitely";
- break;
- case LivenessKind::Maybe:
- OS << "maybe";
- break;
- case LivenessKind::Dead:
- llvm_unreachable("liveness kind of live origins should not be dead.");
- }
- OS << " live at this point\n";
- }
- }
-};
-
-/// The analysis that tracks which origins are live, with granular information
-/// about the causing use fact and confidence level. This is a backward
-/// analysis.
-class AnalysisImpl
- : public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Backward> {
-
-public:
- AnalysisImpl(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
- LivenessMap::Factory &SF)
- : DataflowAnalysis(C, AC, F), FactMgr(F), Factory(SF) {}
- using DataflowAnalysis<AnalysisImpl, Lattice, Direction::Backward>::transfer;
-
- StringRef getAnalysisName() const { return "LiveOrigins"; }
-
- Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); }
-
- /// Merges two lattices by combining liveness information.
- /// When the same origin has different confidence levels, we take the lower
- /// one.
- Lattice join(Lattice L1, Lattice L2) const {
- LivenessMap Merged = L1.LiveOrigins;
- // Take the earliest UseFact to make the join hermetic and commutative.
- auto CombineUseFact = [](const UseFact &A,
- const UseFact &B) -> const UseFact * {
- return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A
- : &B;
- };
- auto CombineLivenessKind = [](LivenessKind K1,
- LivenessKind K2) -> LivenessKind {
- assert(K1 != LivenessKind::Dead && "LivenessKind should not be dead.");
- assert(K2 != LivenessKind::Dead && "LivenessKind should not be dead.");
- // Only return "Must" if both paths are "Must", otherwise Maybe.
- if (K1 == LivenessKind::Must && K2 == LivenessKind::Must)
- return LivenessKind::Must;
- return LivenessKind::Maybe;
- };
- auto CombineLivenessInfo = [&](const LivenessInfo *L1,
- const LivenessInfo *L2) -> LivenessInfo {
- assert((L1 || L2) && "unexpectedly merging 2 empty sets");
- if (!L1)
- return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe);
- if (!L2)
- return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe);
- return LivenessInfo(
- CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact),
- CombineLivenessKind(L1->Kind, L2->Kind));
- };
- return Lattice(utils::join(
- L1.LiveOrigins, L2.LiveOrigins, Factory, CombineLivenessInfo,
- // A symmetric join is required here. If an origin is live on one
- // branch but not the other, its confidence must be demoted to `Maybe`.
- utils::JoinKind::Symmetric));
- }
-
- /// A read operation makes the origin live with definite confidence, as it
- /// dominates this program point. A write operation kills the liveness of
- /// the origin since it overwrites the value.
- Lattice transfer(Lattice In, const UseFact &UF) {
- OriginID OID = UF.getUsedOrigin(FactMgr.getOriginMgr());
- // Write kills liveness.
- if (UF.isWritten())
- return Lattice(Factory.remove(In.LiveOrigins, OID));
- // Read makes origin live with definite confidence (dominates this point).
- return Lattice(Factory.add(In.LiveOrigins, OID,
- LivenessInfo(&UF, LivenessKind::Must)));
- }
-
- /// Issuing a new loan to an origin kills its liveness.
- Lattice transfer(Lattice In, const IssueFact &IF) {
- return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID()));
- }
-
- /// An OriginFlow kills the liveness of the destination origin if `KillDest`
- /// is true. Otherwise, it propagates liveness from destination to source.
- Lattice transfer(Lattice In, const OriginFlowFact &OF) {
- if (!OF.getKillDest())
- return In;
- return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID()));
- }
-
- LivenessMap getLiveOriginsAt(ProgramPoint P) const {
- return getState(P).LiveOrigins;
- }
-
- // Dump liveness values on all test points in the program.
- void dump(llvm::raw_ostream &OS,
- llvm::StringMap<ProgramPoint> TestPoints) const {
- llvm::dbgs() << "==========================================\n";
- llvm::dbgs() << getAnalysisName() << " results:\n";
- llvm::dbgs() << "==========================================\n";
- for (const auto &Entry : TestPoints) {
- OS << "TestPoint: " << Entry.getKey() << "\n";
- getState(Entry.getValue()).dump(OS, FactMgr.getOriginMgr());
- }
- }
-
-private:
- FactManager &FactMgr;
- LivenessMap::Factory &Factory;
-};
-} // namespace
-
-// PImpl wrapper implementation
-class LiveOriginsAnalysis::Impl : public AnalysisImpl {
- using AnalysisImpl::AnalysisImpl;
-};
-
-LiveOriginsAnalysis::LiveOriginsAnalysis(const CFG &C, AnalysisDeclContext &AC,
- FactManager &F,
- LivenessMap::Factory &SF)
- : PImpl(std::make_unique<Impl>(C, AC, F, SF)) {
- PImpl->run();
-}
-
-LiveOriginsAnalysis::~LiveOriginsAnalysis() = default;
-
-LivenessMap LiveOriginsAnalysis::getLiveOriginsAt(ProgramPoint P) const {
- return PImpl->getLiveOriginsAt(P);
-}
-
-void LiveOriginsAnalysis::dump(llvm::raw_ostream &OS,
- llvm::StringMap<ProgramPoint> TestPoints) const {
- PImpl->dump(OS, TestPoints);
-}
-} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
deleted file mode 100644
index 387097e..0000000
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-//===- LoanPropagation.cpp - Loan Propagation Analysis ---------*- 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/Analyses/LifetimeSafety/LoanPropagation.h"
-#include "Dataflow.h"
-#include <memory>
-
-namespace clang::lifetimes::internal {
-namespace {
-/// Represents the dataflow lattice for loan propagation.
-///
-/// This lattice tracks which loans each origin may hold at a given program
-/// point.The lattice has a finite height: An origin's loan set is bounded by
-/// the total number of loans in the function.
-/// TODO(opt): To reduce the lattice size, propagate origins of declarations,
-/// not expressions, because expressions are not visible across blocks.
-struct Lattice {
- /// The map from an origin to the set of loans it contains.
- OriginLoanMap Origins = OriginLoanMap(nullptr);
-
- explicit Lattice(const OriginLoanMap &S) : Origins(S) {}
- Lattice() = default;
-
- bool operator==(const Lattice &Other) const {
- return Origins == Other.Origins;
- }
- bool operator!=(const Lattice &Other) const { return !(*this == Other); }
-
- void dump(llvm::raw_ostream &OS) const {
- OS << "LoanPropagationLattice State:\n";
- if (Origins.isEmpty())
- OS << " <empty>\n";
- for (const auto &Entry : Origins) {
- if (Entry.second.isEmpty())
- OS << " Origin " << Entry.first << " contains no loans\n";
- for (const LoanID &LID : Entry.second)
- OS << " Origin " << Entry.first << " contains Loan " << LID << "\n";
- }
- }
-};
-
-class AnalysisImpl
- : public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Forward> {
-public:
- AnalysisImpl(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
- OriginLoanMap::Factory &OriginLoanMapFactory,
- LoanSet::Factory &LoanSetFactory)
- : DataflowAnalysis(C, AC, F), OriginLoanMapFactory(OriginLoanMapFactory),
- LoanSetFactory(LoanSetFactory) {}
-
- using Base::transfer;
-
- StringRef getAnalysisName() const { return "LoanPropagation"; }
-
- Lattice getInitialState() { return Lattice{}; }
-
- /// Merges two lattices by taking the union of loans for each origin.
- // 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, OriginLoanMapFactory,
- [&](const LoanSet *S1, const LoanSet *S2) {
- assert((S1 || S2) && "unexpectedly merging 2 empty sets");
- if (!S1)
- return *S2;
- if (!S2)
- return *S1;
- return utils::join(*S1, *S2, LoanSetFactory);
- },
- // Asymmetric join is a performance win. For origins present only on one
- // branch, the loan set can be carried over as-is.
- utils::JoinKind::Asymmetric);
- return Lattice(JoinedOrigins);
- }
-
- /// A new loan is issued to the origin. Old loans are erased.
- Lattice transfer(Lattice In, const IssueFact &F) {
- OriginID OID = F.getOriginID();
- LoanID LID = F.getLoanID();
- return Lattice(OriginLoanMapFactory.add(
- In.Origins, OID,
- LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
- }
-
- /// A flow from source to destination. If `KillDest` is true, this replaces
- /// the destination's loans with the source's. Otherwise, the source's loans
- /// are merged into the destination's.
- Lattice transfer(Lattice In, const OriginFlowFact &F) {
- OriginID DestOID = F.getDestOriginID();
- OriginID SrcOID = F.getSrcOriginID();
-
- LoanSet DestLoans =
- F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
- LoanSet SrcLoans = getLoans(In, SrcOID);
- LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
-
- return Lattice(OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans));
- }
-
- LoanSet getLoans(OriginID OID, ProgramPoint P) const {
- return getLoans(getState(P), OID);
- }
-
-private:
- LoanSet getLoans(Lattice L, OriginID OID) const {
- if (auto *Loans = L.Origins.lookup(OID))
- return *Loans;
- return LoanSetFactory.getEmptySet();
- }
-
- OriginLoanMap::Factory &OriginLoanMapFactory;
- LoanSet::Factory &LoanSetFactory;
-};
-} // namespace
-
-class LoanPropagationAnalysis::Impl final : public AnalysisImpl {
- using AnalysisImpl::AnalysisImpl;
-};
-
-LoanPropagationAnalysis::LoanPropagationAnalysis(
- const CFG &C, AnalysisDeclContext &AC, FactManager &F,
- OriginLoanMap::Factory &OriginLoanMapFactory,
- LoanSet::Factory &LoanSetFactory)
- : PImpl(std::make_unique<Impl>(C, AC, F, OriginLoanMapFactory,
- LoanSetFactory)) {
- PImpl->run();
-}
-
-LoanPropagationAnalysis::~LoanPropagationAnalysis() = default;
-
-LoanSet LoanPropagationAnalysis::getLoans(OriginID OID, ProgramPoint P) const {
- return PImpl->getLoans(OID, P);
-}
-} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
deleted file mode 100644
index 2c85a3c..0000000
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-//===- Loans.cpp - Loan Implementation --------------------------*- 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/Analyses/LifetimeSafety/Loans.h"
-
-namespace clang::lifetimes::internal {
-
-void Loan::dump(llvm::raw_ostream &OS) const {
- OS << ID << " (Path: ";
- OS << Path.D->getNameAsString() << ")";
-}
-
-} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
deleted file mode 100644
index ea51a75..0000000
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-//===- Origins.cpp - Origin Implementation -----------------------*- 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/Analyses/LifetimeSafety/Origins.h"
-
-namespace clang::lifetimes::internal {
-
-void OriginManager::dump(OriginID OID, llvm::raw_ostream &OS) const {
- OS << OID << " (";
- Origin O = getOrigin(OID);
- if (const ValueDecl *VD = O.getDecl())
- OS << "Decl: " << VD->getNameAsString();
- else if (const Expr *E = O.getExpr())
- OS << "Expr: " << E->getStmtClassName();
- else
- OS << "Unknown";
- OS << ")";
-}
-
-Origin &OriginManager::addOrigin(OriginID ID, const clang::ValueDecl &D) {
- AllOrigins.emplace_back(ID, &D);
- return AllOrigins.back();
-}
-
-Origin &OriginManager::addOrigin(OriginID ID, const clang::Expr &E) {
- AllOrigins.emplace_back(ID, &E);
- return AllOrigins.back();
-}
-
-// TODO: Mark this method as const once we remove the call to getOrCreate.
-OriginID OriginManager::get(const Expr &E) {
- auto It = ExprToOriginID.find(&E);
- if (It != ExprToOriginID.end())
- return It->second;
- // If the expression itself has no specific origin, and it's a reference
- // to a declaration, its origin is that of the declaration it refers to.
- // For pointer types, where we don't pre-emptively create an origin for the
- // DeclRefExpr itself.
- if (const auto *DRE = dyn_cast<DeclRefExpr>(&E))
- return get(*DRE->getDecl());
- // TODO: This should be an assert(It != ExprToOriginID.end()). The current
- // implementation falls back to getOrCreate to avoid crashing on
- // yet-unhandled pointer expressions, creating an empty origin for them.
- return getOrCreate(E);
-}
-
-OriginID OriginManager::get(const ValueDecl &D) {
- auto It = DeclToOriginID.find(&D);
- // TODO: This should be an assert(It != DeclToOriginID.end()). The current
- // implementation falls back to getOrCreate to avoid crashing on
- // yet-unhandled pointer expressions, creating an empty origin for them.
- if (It == DeclToOriginID.end())
- return getOrCreate(D);
-
- return It->second;
-}
-
-OriginID OriginManager::getOrCreate(const Expr &E) {
- auto It = ExprToOriginID.find(&E);
- if (It != ExprToOriginID.end())
- return It->second;
-
- OriginID NewID = getNextOriginID();
- addOrigin(NewID, E);
- ExprToOriginID[&E] = NewID;
- return NewID;
-}
-
-const Origin &OriginManager::getOrigin(OriginID ID) const {
- assert(ID.Value < AllOrigins.size());
- return AllOrigins[ID.Value];
-}
-
-OriginID OriginManager::getOrCreate(const ValueDecl &D) {
- auto It = DeclToOriginID.find(&D);
- if (It != DeclToOriginID.end())
- return It->second;
- OriginID NewID = getNextOriginID();
- addOrigin(NewID, D);
- DeclToOriginID[&D] = NewID;
- return NewID;
-}
-
-} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Basic/Targets.cpp b/clang/lib/Basic/Targets.cpp
index d9aafc6..b7e8bad 100644
--- a/clang/lib/Basic/Targets.cpp
+++ b/clang/lib/Basic/Targets.cpp
@@ -704,6 +704,10 @@ std::unique_ptr<TargetInfo> AllocateTarget(const llvm::Triple &Triple,
case llvm::Triple::Emscripten:
return std::make_unique<EmscriptenTargetInfo<WebAssembly32TargetInfo>>(
Triple, Opts);
+
+ case llvm::Triple::Linux:
+ return std::make_unique<WALITargetInfo<WebAssembly32TargetInfo>>(Triple,
+ Opts);
case llvm::Triple::UnknownOS:
return std::make_unique<WebAssemblyOSTargetInfo<WebAssembly32TargetInfo>>(
Triple, Opts);
diff --git a/clang/lib/Basic/Targets/OSTargets.h b/clang/lib/Basic/Targets/OSTargets.h
index 6c49a09..bd6ffcf 100644
--- a/clang/lib/Basic/Targets/OSTargets.h
+++ b/clang/lib/Basic/Targets/OSTargets.h
@@ -948,6 +948,23 @@ public:
using WebAssemblyOSTargetInfo<Target>::WebAssemblyOSTargetInfo;
};
+// WALI target
+template <typename Target>
+class LLVM_LIBRARY_VISIBILITY WALITargetInfo
+ : public WebAssemblyOSTargetInfo<Target> {
+ void getOSDefines(const LangOptions &Opts, const llvm::Triple &Triple,
+ MacroBuilder &Builder) const final {
+ WebAssemblyOSTargetInfo<Target>::getOSDefines(Opts, Triple, Builder);
+ // Linux defines; list based off of gcc output
+ DefineStd(Builder, "unix", Opts);
+ DefineStd(Builder, "linux", Opts);
+ Builder.defineMacro("__wali__");
+ }
+
+public:
+ using WebAssemblyOSTargetInfo<Target>::WebAssemblyOSTargetInfo;
+};
+
// Emscripten target
template <typename Target>
class LLVM_LIBRARY_VISIBILITY EmscriptenTargetInfo
diff --git a/clang/lib/Basic/Targets/WebAssembly.h b/clang/lib/Basic/Targets/WebAssembly.h
index eba7422..4de6ce6 100644
--- a/clang/lib/Basic/Targets/WebAssembly.h
+++ b/clang/lib/Basic/Targets/WebAssembly.h
@@ -88,12 +88,23 @@ public:
LongDoubleWidth = LongDoubleAlign = 128;
LongDoubleFormat = &llvm::APFloat::IEEEquad();
MaxAtomicPromoteWidth = MaxAtomicInlineWidth = 64;
- // size_t being unsigned long for both wasm32 and wasm64 makes mangled names
- // more consistent between the two.
- SizeType = UnsignedLong;
- PtrDiffType = SignedLong;
- IntPtrType = SignedLong;
HasUnalignedAccess = true;
+ if (T.isWALI()) {
+ // The WALI ABI is documented here:
+ // https://doc.rust-lang.org/rustc/platform-support/wasm32-wali-linux.html
+ // Currently, this ABI only applies to wasm32 targets and notably requires
+ // 64-bit longs
+ LongAlign = LongWidth = 64;
+ SizeType = UnsignedInt;
+ PtrDiffType = SignedInt;
+ IntPtrType = SignedInt;
+ } else {
+ // size_t being unsigned long for both wasm32 and wasm64 makes mangled
+ // names more consistent between the two.
+ SizeType = UnsignedLong;
+ PtrDiffType = SignedLong;
+ IntPtrType = SignedLong;
+ }
}
StringRef getABI() const override;
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index 25afe8b..a6f10e6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -319,12 +319,6 @@ public:
return cir::ConstantOp::create(*this, loc, cir::IntAttr::get(sInt64Ty, c));
}
- // Creates constant nullptr for pointer type ty.
- cir::ConstantOp getNullPtr(mlir::Type ty, mlir::Location loc) {
- assert(!cir::MissingFeatures::targetCodeGenInfoGetNullPointer());
- return cir::ConstantOp::create(*this, loc, getConstPtrAttr(ty, 0));
- }
-
mlir::Value createNeg(mlir::Value value) {
if (auto intTy = mlir::dyn_cast<cir::IntType>(value.getType())) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 6453843..f9ff37b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -64,3 +64,11 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) {
// Deactivate the cleanup block.
assert(!cir::MissingFeatures::ehCleanupScope());
}
+
+mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
+ if (s.getTryBlock()->body_empty())
+ return mlir::LogicalResult::success();
+
+ cgm.errorNYI("exitCXXTryStmt: CXXTryStmt with non-empty body");
+ return mlir::LogicalResult::success();
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 7a606ee..d71de2f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1297,6 +1297,8 @@ public:
void emitCXXThrowExpr(const CXXThrowExpr *e);
+ mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s);
+
void emitCtorPrologue(const clang::CXXConstructorDecl *ctor,
clang::CXXCtorType ctorType, FunctionArgList &args);
diff --git a/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp b/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp
index 4cf2237..5ba6bcb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp
@@ -73,7 +73,7 @@ CIRGenFunction::getOpenACCDataOperandInfo(const Expr *e) {
// Array sections are special, and we have to treat them that way.
if (const auto *section =
dyn_cast<ArraySectionExpr>(curVarExpr->IgnoreParenImpCasts()))
- origType = ArraySectionExpr::getBaseOriginalType(section);
+ origType = section->getElementType();
mlir::Location exprLoc = cgm.getLoc(curVarExpr->getBeginLoc());
llvm::SmallVector<mlir::Value> bounds;
@@ -84,16 +84,10 @@ CIRGenFunction::getOpenACCDataOperandInfo(const Expr *e) {
e->printPretty(os, nullptr, getContext().getPrintingPolicy());
auto addBoundType = [&](const Expr *e) {
- if (const auto *section = dyn_cast<ArraySectionExpr>(curVarExpr)) {
- QualType baseTy = ArraySectionExpr::getBaseOriginalType(
- section->getBase()->IgnoreParenImpCasts());
- if (auto *at = getContext().getAsArrayType(baseTy))
- boundTypes.push_back(at->getElementType());
- else
- boundTypes.push_back(baseTy->getPointeeType());
- } else {
+ if (const auto *section = dyn_cast<ArraySectionExpr>(curVarExpr))
+ boundTypes.push_back(section->getElementType());
+ else
boundTypes.push_back(curVarExpr->getType());
- }
};
addBoundType(curVarExpr);
@@ -113,8 +107,7 @@ CIRGenFunction::getOpenACCDataOperandInfo(const Expr *e) {
if (const Expr *len = section->getLength()) {
extent = emitOpenACCIntExpr(len);
} else {
- QualType baseTy = ArraySectionExpr::getBaseOriginalType(
- section->getBase()->IgnoreParenImpCasts());
+ QualType baseTy = section->getBaseType();
// We know this is the case as implicit lengths are only allowed for
// array types with a constant size, or a dependent size. AND since
// we are codegen we know we're not dependent.
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 0b8f8bf..cfd48a2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -154,6 +154,8 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
return emitWhileStmt(cast<WhileStmt>(*s));
case Stmt::DoStmtClass:
return emitDoStmt(cast<DoStmt>(*s));
+ case Stmt::CXXTryStmtClass:
+ return emitCXXTryStmt(cast<CXXTryStmt>(*s));
case Stmt::CXXForRangeStmtClass:
return emitCXXForRangeStmt(cast<CXXForRangeStmt>(*s), attr);
case Stmt::OpenACCComputeConstructClass:
@@ -199,7 +201,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
case Stmt::CoroutineBodyStmtClass:
return emitCoroutineBody(cast<CoroutineBodyStmt>(*s));
case Stmt::CoreturnStmtClass:
- case Stmt::CXXTryStmtClass:
case Stmt::IndirectGotoStmtClass:
case Stmt::OMPParallelDirectiveClass:
case Stmt::OMPTaskwaitDirectiveClass:
diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
index df7a1a3..3fc5b06 100644
--- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
+++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
@@ -4,6 +4,7 @@ add_clang_library(MLIRCIRTransforms
FlattenCFG.cpp
HoistAllocas.cpp
LoweringPrepare.cpp
+ LoweringPrepareItaniumCXXABI.cpp
GotoSolver.cpp
DEPENDS
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 706e54f..dbff0b9 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
+#include "LoweringPrepareCXXABI.h"
#include "PassDetail.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/Module.h"
@@ -62,6 +63,7 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
void lowerComplexMulOp(cir::ComplexMulOp op);
void lowerUnaryOp(cir::UnaryOp op);
void lowerGlobalOp(cir::GlobalOp op);
+ void lowerDynamicCastOp(cir::DynamicCastOp op);
void lowerArrayDtor(cir::ArrayDtor op);
void lowerArrayCtor(cir::ArrayCtor op);
@@ -91,6 +93,9 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
clang::ASTContext *astCtx;
+ // Helper for lowering C++ ABI specific operations.
+ std::shared_ptr<cir::LoweringPrepareCXXABI> cxxABI;
+
/// Tracks current module.
mlir::ModuleOp mlirModule;
@@ -101,7 +106,24 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
/// List of ctors and their priorities to be called before main()
llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList;
- void setASTContext(clang::ASTContext *c) { astCtx = c; }
+ void setASTContext(clang::ASTContext *c) {
+ astCtx = c;
+ switch (c->getCXXABIKind()) {
+ case clang::TargetCXXABI::GenericItanium:
+ // We'll need X86-specific support for handling vaargs lowering, but for
+ // now the Itanium ABI will work.
+ assert(!cir::MissingFeatures::loweringPrepareX86CXXABI());
+ cxxABI.reset(cir::LoweringPrepareCXXABI::createItaniumABI());
+ break;
+ case clang::TargetCXXABI::GenericAArch64:
+ case clang::TargetCXXABI::AppleARM64:
+ assert(!cir::MissingFeatures::loweringPrepareAArch64XXABI());
+ cxxABI.reset(cir::LoweringPrepareCXXABI::createItaniumABI());
+ break;
+ default:
+ llvm_unreachable("NYI");
+ }
+ }
};
} // namespace
@@ -850,6 +872,17 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() {
cir::ReturnOp::create(builder, f.getLoc());
}
+void LoweringPreparePass::lowerDynamicCastOp(DynamicCastOp op) {
+ CIRBaseBuilderTy builder(getContext());
+ builder.setInsertionPointAfter(op);
+
+ assert(astCtx && "AST context is not available during lowering prepare");
+ auto loweredValue = cxxABI->lowerDynamicCast(builder, *astCtx, op);
+
+ op.replaceAllUsesWith(loweredValue);
+ op.erase();
+}
+
static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,
clang::ASTContext *astCtx,
mlir::Operation *op, mlir::Type eltTy,
@@ -954,6 +987,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
lowerComplexMulOp(complexMul);
else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op))
lowerGlobalOp(glob);
+ else if (auto dynamicCast = mlir::dyn_cast<cir::DynamicCastOp>(op))
+ lowerDynamicCastOp(dynamicCast);
else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op))
lowerUnaryOp(unary);
}
@@ -967,8 +1002,8 @@ void LoweringPreparePass::runOnOperation() {
op->walk([&](mlir::Operation *op) {
if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp,
- cir::ComplexMulOp, cir::ComplexDivOp, cir::GlobalOp,
- cir::UnaryOp>(op))
+ cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp,
+ cir::GlobalOp, cir::UnaryOp>(op))
opsToTransform.push_back(op);
});
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepareCXXABI.h b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareCXXABI.h
new file mode 100644
index 0000000..2582c332
--- /dev/null
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareCXXABI.h
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 provides the LoweringPrepareCXXABI class, which is the base class
+// for ABI specific functionalities that are required during LLVM lowering
+// prepare.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CIR_DIALECT_TRANSFORMS__LOWERINGPREPARECXXABI_H
+#define CIR_DIALECT_TRANSFORMS__LOWERINGPREPARECXXABI_H
+
+#include "mlir/IR/Value.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+
+namespace cir {
+
+class LoweringPrepareCXXABI {
+public:
+ static LoweringPrepareCXXABI *createItaniumABI();
+
+ virtual ~LoweringPrepareCXXABI() {}
+
+ virtual mlir::Value lowerDynamicCast(CIRBaseBuilderTy &builder,
+ clang::ASTContext &astCtx,
+ cir::DynamicCastOp op) = 0;
+};
+
+} // namespace cir
+
+#endif // CIR_DIALECT_TRANSFORMS__LOWERINGPREPARECXXABI_H
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepareItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareItaniumCXXABI.cpp
new file mode 100644
index 0000000..7d3c711
--- /dev/null
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareItaniumCXXABI.cpp
@@ -0,0 +1,126 @@
+//===--------------------------------------------------------------------===//
+//
+// 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 provides Itanium C++ ABI specific code
+// that is used during LLVMIR lowering prepare.
+//
+//===--------------------------------------------------------------------===//
+
+#include "LoweringPrepareCXXABI.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/Value.h"
+#include "mlir/IR/ValueRange.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
+#include "clang/CIR/Dialect/IR/CIRDataLayout.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/MissingFeatures.h"
+
+class LoweringPrepareItaniumCXXABI : public cir::LoweringPrepareCXXABI {
+public:
+ mlir::Value lowerDynamicCast(cir::CIRBaseBuilderTy &builder,
+ clang::ASTContext &astCtx,
+ cir::DynamicCastOp op) override;
+};
+
+cir::LoweringPrepareCXXABI *cir::LoweringPrepareCXXABI::createItaniumABI() {
+ return new LoweringPrepareItaniumCXXABI();
+}
+
+static void buildBadCastCall(cir::CIRBaseBuilderTy &builder, mlir::Location loc,
+ mlir::FlatSymbolRefAttr badCastFuncRef) {
+ builder.createCallOp(loc, badCastFuncRef, cir::VoidType(),
+ mlir::ValueRange{});
+ // TODO(cir): Set the 'noreturn' attribute on the function.
+ assert(!cir::MissingFeatures::opFuncNoReturn());
+ cir::UnreachableOp::create(builder, loc);
+ builder.clearInsertionPoint();
+}
+
+static mlir::Value
+buildDynamicCastAfterNullCheck(cir::CIRBaseBuilderTy &builder,
+ cir::DynamicCastOp op) {
+ mlir::Location loc = op->getLoc();
+ mlir::Value srcValue = op.getSrc();
+ cir::DynamicCastInfoAttr castInfo = op.getInfo().value();
+
+ // TODO(cir): consider address space
+ assert(!cir::MissingFeatures::addressSpace());
+
+ mlir::Value srcPtr = builder.createBitcast(srcValue, builder.getVoidPtrTy());
+ cir::ConstantOp srcRtti = builder.getConstant(loc, castInfo.getSrcRtti());
+ cir::ConstantOp destRtti = builder.getConstant(loc, castInfo.getDestRtti());
+ cir::ConstantOp offsetHint =
+ builder.getConstant(loc, castInfo.getOffsetHint());
+
+ mlir::FlatSymbolRefAttr dynCastFuncRef = castInfo.getRuntimeFunc();
+ mlir::Value dynCastFuncArgs[4] = {srcPtr, srcRtti, destRtti, offsetHint};
+
+ mlir::Value castedPtr =
+ builder
+ .createCallOp(loc, dynCastFuncRef, builder.getVoidPtrTy(),
+ dynCastFuncArgs)
+ .getResult();
+
+ assert(mlir::isa<cir::PointerType>(castedPtr.getType()) &&
+ "the return value of __dynamic_cast should be a ptr");
+
+ /// C++ [expr.dynamic.cast]p9:
+ /// A failed cast to reference type throws std::bad_cast
+ if (op.isRefCast()) {
+ // Emit a cir.if that checks the casted value.
+ mlir::Value castedValueIsNull = builder.createPtrIsNull(castedPtr);
+ builder.create<cir::IfOp>(
+ loc, castedValueIsNull, false, [&](mlir::OpBuilder &, mlir::Location) {
+ buildBadCastCall(builder, loc, castInfo.getBadCastFunc());
+ });
+ }
+
+ // Note that castedPtr is a void*. Cast it to a pointer to the destination
+ // type before return.
+ return builder.createBitcast(castedPtr, op.getType());
+}
+
+static mlir::Value
+buildDynamicCastToVoidAfterNullCheck(cir::CIRBaseBuilderTy &builder,
+ clang::ASTContext &astCtx,
+ cir::DynamicCastOp op) {
+ llvm_unreachable("dynamic cast to void is NYI");
+}
+
+mlir::Value
+LoweringPrepareItaniumCXXABI::lowerDynamicCast(cir::CIRBaseBuilderTy &builder,
+ clang::ASTContext &astCtx,
+ cir::DynamicCastOp op) {
+ mlir::Location loc = op->getLoc();
+ mlir::Value srcValue = op.getSrc();
+
+ assert(!cir::MissingFeatures::emitTypeCheck());
+
+ if (op.isRefCast())
+ return buildDynamicCastAfterNullCheck(builder, op);
+
+ mlir::Value srcValueIsNotNull = builder.createPtrToBoolCast(srcValue);
+ return builder
+ .create<cir::TernaryOp>(
+ loc, srcValueIsNotNull,
+ [&](mlir::OpBuilder &, mlir::Location) {
+ mlir::Value castedValue =
+ op.isCastToVoid()
+ ? buildDynamicCastToVoidAfterNullCheck(builder, astCtx, op)
+ : buildDynamicCastAfterNullCheck(builder, op);
+ builder.createYield(loc, castedValue);
+ },
+ [&](mlir::OpBuilder &, mlir::Location) {
+ builder.createYield(
+ loc, builder.getNullPtr(op.getType(), loc).getResult());
+ })
+ .getResult();
+}
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index a1ecfc7..26e0ba9 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1739,7 +1739,6 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
const mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage());
const StringRef symbol = op.getSymName();
SmallVector<mlir::NamedAttribute> attributes;
- mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
if (init.has_value()) {
if (mlir::isa<cir::FPAttr, cir::IntAttr, cir::BoolAttr>(init.value())) {
@@ -1771,6 +1770,7 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
}
// Rewrite op.
+ mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()),
alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr, attributes);
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 85a13357..40ea513 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -6836,6 +6836,8 @@ const ToolChain &Driver::getToolChain(const ArgList &Args,
TC = std::make_unique<toolchains::VEToolChain>(*this, Target, Args);
else if (Target.isOHOSFamily())
TC = std::make_unique<toolchains::OHOS>(*this, Target, Args);
+ else if (Target.isWALI())
+ TC = std::make_unique<toolchains::WebAssembly>(*this, Target, Args);
else
TC = std::make_unique<toolchains::Linux>(*this, Target, Args);
break;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 9abaf79..e9ca8ce 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -29,7 +29,7 @@
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
#include "clang/Analysis/Analyses/Consumed.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
+#include "clang/Analysis/Analyses/LifetimeSafety.h"
#include "clang/Analysis/Analyses/ReachableCode.h"
#include "clang/Analysis/Analyses/ThreadSafety.h"
#include "clang/Analysis/Analyses/UninitializedValues.h"
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 0ebf56e..51e0ee1 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -111,7 +111,6 @@ add_clang_library(clangSema
clangAPINotes
clangAST
clangAnalysis
- clangAnalysisLifetimeSafety
clangBasic
clangEdit
clangLex
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 8aebf53..e8a7ad3 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -10,7 +10,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Sema.h"
diff --git a/clang/lib/Sema/SemaAMDGPU.cpp b/clang/lib/Sema/SemaAMDGPU.cpp
index 45fe80d..e32f437 100644
--- a/clang/lib/Sema/SemaAMDGPU.cpp
+++ b/clang/lib/Sema/SemaAMDGPU.cpp
@@ -11,9 +11,9 @@
//===----------------------------------------------------------------------===//
#include "clang/Sema/SemaAMDGPU.h"
+#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/TargetBuiltins.h"
-#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Sema/Ownership.h"
#include "clang/Sema/Sema.h"
#include "llvm/Support/AMDGPUAddrSpace.h"
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 0d8d0fa..35cdfbf 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -17,7 +17,7 @@
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/TypeLoc.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Lexer.h"
#include "clang/Sema/SemaObjC.h"
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index f4df63c..9cbd1bd 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -604,6 +604,10 @@ ConstraintSatisfactionChecker::SubstitutionInTemplateArguments(
return std::nullopt;
const NormalizedConstraint::OccurenceList &Used =
Constraint.mappingOccurenceList();
+ // The empty MLTAL situation should only occur when evaluating non-dependent
+ // constraints.
+ if (!MLTAL.getNumSubstitutedLevels())
+ MLTAL.addOuterTemplateArguments(TD, {}, /*Final=*/false);
SubstitutedOuterMost =
llvm::to_vector_of<TemplateArgument>(MLTAL.getOutermost());
unsigned Offset = 0;
@@ -623,9 +627,7 @@ ConstraintSatisfactionChecker::SubstitutionInTemplateArguments(
if (Offset < SubstitutedOuterMost.size())
SubstitutedOuterMost.erase(SubstitutedOuterMost.begin() + Offset);
- MLTAL.replaceOutermostTemplateArguments(
- const_cast<NamedDecl *>(Constraint.getConstraintDecl()),
- SubstitutedOuterMost);
+ MLTAL.replaceOutermostTemplateArguments(TD, SubstitutedOuterMost);
return std::move(MLTAL);
}
@@ -956,11 +958,20 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
? Constraint.getPackSubstitutionIndex()
: PackSubstitutionIndex;
- Sema::InstantiatingTemplate _(S, ConceptId->getBeginLoc(),
- Sema::InstantiatingTemplate::ConstraintsCheck{},
- ConceptId->getNamedConcept(),
- MLTAL.getInnermost(),
- Constraint.getSourceRange());
+ Sema::InstantiatingTemplate InstTemplate(
+ S, ConceptId->getBeginLoc(),
+ Sema::InstantiatingTemplate::ConstraintsCheck{},
+ ConceptId->getNamedConcept(),
+ // We may have empty template arguments when checking non-dependent
+ // nested constraint expressions.
+ // In such cases, non-SFINAE errors would have already been diagnosed
+ // during parameter mapping substitution, so the instantiating template
+ // arguments are less useful here.
+ MLTAL.getNumSubstitutedLevels() ? MLTAL.getInnermost()
+ : ArrayRef<TemplateArgument>{},
+ Constraint.getSourceRange());
+ if (InstTemplate.isInvalid())
+ return ExprError();
unsigned Size = Satisfaction.Details.size();
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 4230ea7..01abc1f 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -166,6 +166,11 @@ static void diagnoseUseOfInternalDeclInInlineFunction(Sema &S,
// This is disabled under C++; there are too many ways for this to fire in
// contexts where the warning is a false positive, or where it is technically
// correct but benign.
+ //
+ // WG14 N3622 which removed the constraint entirely in C2y. It is left
+ // enabled in earlier language modes because this is a constraint in those
+ // language modes. But in C2y mode, we still want to issue the "incompatible
+ // with previous standards" diagnostic, too.
if (S.getLangOpts().CPlusPlus)
return;
@@ -190,16 +195,17 @@ static void diagnoseUseOfInternalDeclInInlineFunction(Sema &S,
// This last can give us false negatives, but it's better than warning on
// wrappers for simple C library functions.
const FunctionDecl *UsedFn = dyn_cast<FunctionDecl>(D);
- bool DowngradeWarning = S.getSourceManager().isInMainFile(Loc);
- if (!DowngradeWarning && UsedFn)
- DowngradeWarning = UsedFn->isInlined() || UsedFn->hasAttr<ConstAttr>();
-
- S.Diag(Loc, DowngradeWarning ? diag::ext_internal_in_extern_inline_quiet
- : diag::ext_internal_in_extern_inline)
- << /*IsVar=*/!UsedFn << D;
+ unsigned DiagID;
+ if (S.getLangOpts().C2y)
+ DiagID = diag::warn_c2y_compat_internal_in_extern_inline;
+ else if ((UsedFn && (UsedFn->isInlined() || UsedFn->hasAttr<ConstAttr>())) ||
+ S.getSourceManager().isInMainFile(Loc))
+ DiagID = diag::ext_internal_in_extern_inline_quiet;
+ else
+ DiagID = diag::ext_internal_in_extern_inline;
+ S.Diag(Loc, DiagID) << /*IsVar=*/!UsedFn << D;
S.MaybeSuggestAddingStaticToDecl(Current);
-
S.Diag(D->getCanonicalDecl()->getLocation(), diag::note_entity_declared_at)
<< D;
}
diff --git a/clang/lib/Sema/SemaOpenACC.cpp b/clang/lib/Sema/SemaOpenACC.cpp
index 4824b5a..f3969a9 100644
--- a/clang/lib/Sema/SemaOpenACC.cpp
+++ b/clang/lib/Sema/SemaOpenACC.cpp
@@ -2759,7 +2759,7 @@ OpenACCPrivateRecipe SemaOpenACC::CreatePrivateInitRecipe(const Expr *VarExpr) {
// Array sections are special, and we have to treat them that way.
if (const auto *ASE =
dyn_cast<ArraySectionExpr>(VarExpr->IgnoreParenImpCasts()))
- VarTy = ArraySectionExpr::getBaseOriginalType(ASE);
+ VarTy = ASE->getElementType();
VarDecl *AllocaDecl = CreateAllocaDecl(
getASTContext(), SemaRef.getCurContext(), VarExpr->getBeginLoc(),
@@ -2795,7 +2795,7 @@ SemaOpenACC::CreateFirstPrivateInitRecipe(const Expr *VarExpr) {
// Array sections are special, and we have to treat them that way.
if (const auto *ASE =
dyn_cast<ArraySectionExpr>(VarExpr->IgnoreParenImpCasts()))
- VarTy = ArraySectionExpr::getBaseOriginalType(ASE);
+ VarTy = ASE->getElementType();
VarDecl *AllocaDecl = CreateAllocaDecl(
getASTContext(), SemaRef.getCurContext(), VarExpr->getBeginLoc(),
@@ -2896,7 +2896,7 @@ OpenACCReductionRecipe SemaOpenACC::CreateReductionInitRecipe(
// Array sections are special, and we have to treat them that way.
if (const auto *ASE =
dyn_cast<ArraySectionExpr>(VarExpr->IgnoreParenImpCasts()))
- VarTy = ArraySectionExpr::getBaseOriginalType(ASE);
+ VarTy = ASE->getElementType();
VarDecl *AllocaDecl = CreateAllocaDecl(
getASTContext(), SemaRef.getCurContext(), VarExpr->getBeginLoc(),
diff --git a/clang/lib/Sema/SemaOpenACCClause.cpp b/clang/lib/Sema/SemaOpenACCClause.cpp
index b086929..881e960 100644
--- a/clang/lib/Sema/SemaOpenACCClause.cpp
+++ b/clang/lib/Sema/SemaOpenACCClause.cpp
@@ -1915,51 +1915,34 @@ SemaOpenACC::ActOnClause(ArrayRef<const OpenACCClause *> ExistingClauses,
return Result;
}
-/// OpenACC 3.3 section 2.5.15:
-/// At a mininmum, the supported data types include ... the numerical data types
-/// in C, C++, and Fortran.
-///
-/// If the reduction var is a composite variable, each
-/// member of the composite variable must be a supported datatype for the
-/// reduction operation.
-ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind,
- OpenACCReductionOperator ReductionOp,
- Expr *VarExpr) {
- // For now, we only support 'scalar' types, or composites/arrays of scalar
- // types.
- VarExpr = VarExpr->IgnoreParenCasts();
+bool SemaOpenACC::CheckReductionVarType(Expr *VarExpr) {
SourceLocation VarLoc = VarExpr->getBeginLoc();
SmallVector<PartialDiagnosticAt> Notes;
- QualType CurType = VarExpr->getType();
-
- // For array like things, the expression can either be an array element
- // (subscript expr), array section, or array type. Peel those off, and add
- // notes in case we find an illegal kind. We'll allow scalar or composite of
- // scalars inside of this.
- if (auto *ASE = dyn_cast<ArraySectionExpr>(VarExpr)) {
- QualType BaseType = ArraySectionExpr::getBaseOriginalType(ASE);
+ // The standard isn't clear how many levels of 'array element' or 'subarray'
+ // are permitted, but we can handle as many as we need, so we'll strip them
+ // off here. This will result in CurType being the actual 'type' of the
+ // expression, which is what we are looking to check.
+ QualType CurType = isa<ArraySectionExpr>(VarExpr)
+ ? ArraySectionExpr::getBaseOriginalType(VarExpr)
+ : VarExpr->getType();
+
+ // This can happen when we have a dependent type in an array element that the
+ // above function has tried to 'unwrap'. Since this can only happen with
+ // dependence, just let it go.
+ if (CurType.isNull())
+ return false;
- PartialDiagnostic PD = PDiag(diag::note_acc_reduction_array)
- << diag::OACCReductionArray::Section << BaseType;
- Notes.push_back({ASE->getBeginLoc(), PD});
-
- CurType = getASTContext().getBaseElementType(BaseType);
- } else if (auto *SubExpr = dyn_cast<ArraySubscriptExpr>(VarExpr)) {
- // Array subscript already results in the type of the thing as its type, so
- // there is no type to change here.
- PartialDiagnostic PD =
- PDiag(diag::note_acc_reduction_array)
- << diag::OACCReductionArray::Subscript
- << SubExpr->getBase()->IgnoreParenImpCasts()->getType();
- Notes.push_back({SubExpr->getBeginLoc(), PD});
- } else if (auto *AT = getASTContext().getAsArrayType(CurType)) {
+ // If we are still an array type, we allow 1 level of 'unpeeling' of the
+ // array. The standard isn't clear here whether this is allowed, but
+ // array-of-valid-things makes sense.
+ if (auto *AT = getASTContext().getAsArrayType(CurType)) {
// If we're already the array type, peel off the array and leave the element
// type.
- CurType = getASTContext().getBaseElementType(AT);
PartialDiagnostic PD = PDiag(diag::note_acc_reduction_array)
<< diag::OACCReductionArray::ArrayTy << CurType;
Notes.push_back({VarLoc, PD});
+ CurType = AT->getElementType();
}
auto IsValidMemberOfComposite = [](QualType Ty) {
@@ -1974,31 +1957,26 @@ ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind,
for (auto [Loc, PD] : Notes)
Diag(Loc, PD);
- Diag(VarLoc, diag::note_acc_reduction_type_summary);
+ return Diag(VarLoc, diag::note_acc_reduction_type_summary);
};
// If the type is already scalar, or is dependent, just give up.
if (IsValidMemberOfComposite(CurType)) {
// Nothing to do here, is valid.
} else if (auto *RD = CurType->getAsRecordDecl()) {
- if (!RD->isStruct() && !RD->isClass()) {
- EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
- << RD << diag::OACCReductionTy::NotClassStruct);
- return ExprError();
- }
+ if (!RD->isStruct() && !RD->isClass())
+ return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
+ << RD
+ << diag::OACCReductionTy::NotClassStruct);
- if (!RD->isCompleteDefinition()) {
- EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
- << RD << diag::OACCReductionTy::NotComplete);
- return ExprError();
- }
+ if (!RD->isCompleteDefinition())
+ return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
+ << RD << diag::OACCReductionTy::NotComplete);
if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD);
- CXXRD && !CXXRD->isAggregate()) {
- EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
- << CXXRD << diag::OACCReductionTy::NotAgg);
- return ExprError();
- }
+ CXXRD && !CXXRD->isAggregate())
+ return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
+ << CXXRD << diag::OACCReductionTy::NotAgg);
for (FieldDecl *FD : RD->fields()) {
if (!IsValidMemberOfComposite(FD->getType())) {
@@ -2007,17 +1985,37 @@ ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind,
<< FD->getName() << RD->getName();
Notes.push_back({FD->getBeginLoc(), PD});
// TODO: member here.note_acc_reduction_member_of_composite
- EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
- << FD->getType()
- << diag::OACCReductionTy::MemberNotScalar);
- return ExprError();
+ return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
+ << FD->getType()
+ << diag::OACCReductionTy::MemberNotScalar);
}
}
} else {
- EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
- << CurType << diag::OACCReductionTy::NotScalar);
+ return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type)
+ << CurType
+ << diag::OACCReductionTy::NotScalar);
}
+ return false;
+}
+
+/// OpenACC 3.3 section 2.5.15:
+/// At a mininmum, the supported data types include ... the numerical data types
+/// in C, C++, and Fortran.
+///
+/// If the reduction var is a composite variable, each
+/// member of the composite variable must be a supported datatype for the
+/// reduction operation.
+ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind,
+ OpenACCReductionOperator ReductionOp,
+ Expr *VarExpr) {
+ // For now, we only support 'scalar' types, or composites/arrays of scalar
+ // types.
+ VarExpr = VarExpr->IgnoreParenCasts();
+
+ if (CheckReductionVarType(VarExpr))
+ return ExprError();
+
// OpenACC3.3: 2.9.11: Reduction clauses on nested constructs for the same
// reduction 'var' must have the same reduction operator.
if (!VarExpr->isInstantiationDependent()) {