[clang] [llvm] [LifetimeSafety] Implement support for lifetimebound attribute (PR #158489)
Utkarsh Saxena via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 21 12:47:07 PDT 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/158489
>From f105e5a5c094e3bfc8f3ceb6331651eaed5823e9 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Fri, 19 Sep 2025 20:00:17 +0000
Subject: [PATCH 1/3] fix-add-immutable-set
---
clang/lib/Analysis/LifetimeSafety.cpp | 12 ++----
llvm/include/llvm/ADT/ImmutableSet.h | 49 ++++++++++++++++++-------
llvm/unittests/ADT/ImmutableSetTest.cpp | 31 ++++++++++++++++
3 files changed, 70 insertions(+), 22 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index d016c6f12e82e..0dd5716d93fb6 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -910,13 +910,10 @@ template <typename T>
static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
llvm::ImmutableSet<T> B,
typename llvm::ImmutableSet<T>::Factory &F) {
- if (A == B)
- return A;
if (A.getHeight() < B.getHeight())
std::swap(A, B);
for (const T &E : B)
- if (!A.contains(E))
- A = F.add(A, E);
+ A = F.add(A, E);
return A;
}
@@ -950,11 +947,10 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
for (const auto &Entry : B) {
const K &Key = Entry.first;
const V &ValB = Entry.second;
- const V *ValA = A.lookup(Key);
- if (!ValA)
- A = F.add(A, Key, ValB);
- else if (*ValA != ValB)
+ if (const V *ValA = A.lookup(Key))
A = F.add(A, Key, JoinValues(*ValA, ValB));
+ else
+ A = F.add(A, Key, ValB);
}
return A;
}
diff --git a/llvm/include/llvm/ADT/ImmutableSet.h b/llvm/include/llvm/ADT/ImmutableSet.h
index ac86f43b2048e..017585a47ddd6 100644
--- a/llvm/include/llvm/ADT/ImmutableSet.h
+++ b/llvm/include/llvm/ADT/ImmutableSet.h
@@ -531,7 +531,7 @@ class ImutAVLFactory {
/// add_internal - Creates a new tree that includes the specified
/// data and the data from the original tree. If the original tree
/// already contained the data item, the original tree is returned.
- TreeTy* add_internal(value_type_ref V, TreeTy* T) {
+ TreeTy *add_internal(value_type_ref V, TreeTy *T) {
if (isEmpty(T))
return createNode(T, V, T);
assert(!T->isMutable());
@@ -539,19 +539,34 @@ class ImutAVLFactory {
key_type_ref K = ImutInfo::KeyOfValue(V);
key_type_ref KCurrent = ImutInfo::KeyOfValue(getValue(T));
- if (ImutInfo::isEqual(K,KCurrent))
+ if (ImutInfo::isEqual(K, KCurrent)) {
+ // If both key and value are same, return the original tree.
+ if (ImutInfo::isDataEqual(ImutInfo::DataOfValue(V),
+ ImutInfo::DataOfValue(getValue(T))))
+ return T;
+ // Otherwise create a new node with the new value.
return createNode(getLeft(T), V, getRight(T));
- else if (ImutInfo::isLess(K,KCurrent))
- return balanceTree(add_internal(V, getLeft(T)), getValue(T), getRight(T));
+ }
+
+ TreeTy *NewL = getLeft(T);
+ TreeTy *NewR = getRight(T);
+ if (ImutInfo::isLess(K, KCurrent))
+ NewL = add_internal(V, NewL);
else
- return balanceTree(getLeft(T), getValue(T), add_internal(V, getRight(T)));
+ NewR = add_internal(V, NewR);
+
+ // If no changes were made, return the original tree. Otherwise, balance the
+ // tree and return the new root.
+ return NewL == getLeft(T) && NewR == getRight(T)
+ ? T
+ : balanceTree(NewL, getValue(T), NewR);
}
/// remove_internal - Creates a new tree that includes all the data
/// from the original tree except the specified data. If the
/// specified data did not exist in the original tree, the original
/// tree is returned.
- TreeTy* remove_internal(key_type_ref K, TreeTy* T) {
+ TreeTy *remove_internal(key_type_ref K, TreeTy *T) {
if (isEmpty(T))
return T;
@@ -559,15 +574,21 @@ class ImutAVLFactory {
key_type_ref KCurrent = ImutInfo::KeyOfValue(getValue(T));
- if (ImutInfo::isEqual(K,KCurrent)) {
+ if (ImutInfo::isEqual(K, KCurrent))
return combineTrees(getLeft(T), getRight(T));
- } else if (ImutInfo::isLess(K,KCurrent)) {
- return balanceTree(remove_internal(K, getLeft(T)),
- getValue(T), getRight(T));
- } else {
- return balanceTree(getLeft(T), getValue(T),
- remove_internal(K, getRight(T)));
- }
+
+ TreeTy *NewL = getLeft(T);
+ TreeTy *NewR = getRight(T);
+ if (ImutInfo::isLess(K, KCurrent))
+ NewL = remove_internal(K, NewL);
+ else
+ NewR = remove_internal(K, NewR);
+
+ // If no changes were made, return the original tree. Otherwise, balance the
+ // tree and return the new root.
+ return NewL == getLeft(T) && NewR == getRight(T)
+ ? T
+ : balanceTree(NewL, getValue(T), NewR);
}
TreeTy* combineTrees(TreeTy* L, TreeTy* R) {
diff --git a/llvm/unittests/ADT/ImmutableSetTest.cpp b/llvm/unittests/ADT/ImmutableSetTest.cpp
index c0bde4c4d680b..87bc2a8da4bad 100644
--- a/llvm/unittests/ADT/ImmutableSetTest.cpp
+++ b/llvm/unittests/ADT/ImmutableSetTest.cpp
@@ -164,4 +164,35 @@ TEST_F(ImmutableSetTest, IterLongSetTest) {
ASSERT_EQ(6, i);
}
+TEST_F(ImmutableSetTest, AddIfNotFoundTest) {
+ ImmutableSet<long>::Factory f(/*canonicalize=*/false);
+ ImmutableSet<long> S = f.getEmptySet();
+ S = f.add(S, 1);
+ S = f.add(S, 2);
+ S = f.add(S, 3);
+
+ ImmutableSet<long> T1 = f.add(S, 1);
+ ImmutableSet<long> T2 = f.add(S, 2);
+ ImmutableSet<long> T3 = f.add(S, 3);
+ EXPECT_EQ(S.getRoot(), T1.getRoot());
+ EXPECT_EQ(S.getRoot(), T2.getRoot());
+ EXPECT_EQ(S.getRoot(), T3.getRoot());
+
+ ImmutableSet<long> U = f.add(S, 4);
+ EXPECT_NE(S.getRoot(), U.getRoot());
+}
+
+TEST_F(ImmutableSetTest, RemoveIfNotFoundTest) {
+ ImmutableSet<long>::Factory f(/*canonicalize=*/false);
+ ImmutableSet<long> S = f.getEmptySet();
+ S = f.add(S, 1);
+ S = f.add(S, 2);
+ S = f.add(S, 3);
+
+ ImmutableSet<long> T = f.remove(S, 4);
+ EXPECT_EQ(S.getRoot(), T.getRoot());
+
+ ImmutableSet<long> U = f.remove(S, 3);
+ EXPECT_NE(S.getRoot(), U.getRoot());
}
+} // namespace
>From 0775d9ec1d9dffb1b2e29f28d986384660ae77d2 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Fri, 19 Sep 2025 21:30:46 +0000
Subject: [PATCH 2/3] no-canonicalize
---
clang/lib/Analysis/LifetimeSafety.cpp | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index 0dd5716d93fb6..da4af42853e55 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -966,9 +966,13 @@ using ExpiredLoanMap = llvm::ImmutableMap<LoanID, const ExpireFact *>;
/// An object to hold the factories for immutable collections, ensuring
/// that all created states share the same underlying memory management.
struct LifetimeFactory {
- OriginLoanMap::Factory OriginMapFactory;
- LoanSet::Factory LoanSetFactory;
- ExpiredLoanMap::Factory ExpiredLoanMapFactory;
+ llvm::BumpPtrAllocator Allocator;
+ OriginLoanMap::Factory OriginMapFactory =
+ OriginLoanMap::Factory(Allocator, /*canonicalize=*/false);
+ LoanSet::Factory LoanSetFactory =
+ LoanSet::Factory(Allocator, /*canonicalize=*/false);
+ ExpiredLoanMap::Factory ExpiredLoanMapFactory =
+ ExpiredLoanMap::Factory(Allocator, /*canonicalize=*/false);
};
/// Represents the dataflow lattice for loan propagation.
>From 1c079e3c3d94e8c7632424c9c098c83b061f55b9 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 14 Sep 2025 14:46:45 +0000
Subject: [PATCH 3/3] lifetime-analysis-lifetimebound
---
.../clang/Analysis/Analyses/LifetimeSafety.h | 11 +-
clang/lib/Analysis/LifetimeSafety.cpp | 195 +++++++++----
.../test/Analysis/LifetimeSafety/benchmark.py | 2 +-
.../Sema/warn-lifetime-safety-dataflow.cpp | 155 +++++------
clang/test/Sema/warn-lifetime-safety.cpp | 149 ++++++++++
.../unittests/Analysis/LifetimeSafetyTest.cpp | 257 +++++++++++++++++-
6 files changed, 636 insertions(+), 133 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
index 7e1bfc903083e..512cb76cd6349 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
@@ -75,13 +75,14 @@ template <typename Tag> struct ID {
}
};
-template <typename Tag>
-inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
- return OS << ID.Value;
-}
-
using LoanID = ID<struct LoanTag>;
using OriginID = ID<struct OriginTag>;
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
+ return OS << ID.Value;
+}
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
+ return OS << ID.Value;
+}
// Using LLVM's immutable collections is efficient for dataflow analysis
// as it avoids deep copies during state transitions.
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index da4af42853e55..5eaa5e52ca3e9 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -212,8 +212,10 @@ class Fact {
/// A loan expires as its underlying storage is freed (e.g., variable goes
/// out of scope).
Expire,
+ /// The loan set of an origin is cleared.
+ KillOrigin,
/// An origin is propagated from a source to a destination (e.g., p = q).
- AssignOrigin,
+ OriginFlow,
/// An origin escapes the function by flowing into the return value.
ReturnOfOrigin,
/// An origin is used (eg. dereferencing a pointer).
@@ -285,22 +287,24 @@ class ExpireFact : public Fact {
}
};
-class AssignOriginFact : public Fact {
+class OriginFlowFact : public Fact {
OriginID OIDDest;
OriginID OIDSrc;
public:
static bool classof(const Fact *F) {
- return F->getKind() == Kind::AssignOrigin;
+ return F->getKind() == Kind::OriginFlow;
}
- AssignOriginFact(OriginID OIDDest, OriginID OIDSrc)
- : Fact(Kind::AssignOrigin), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
+ OriginFlowFact(OriginID OIDDest, OriginID OIDSrc)
+ : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
+
OriginID getDestOriginID() const { return OIDDest; }
OriginID getSrcOriginID() const { return OIDSrc; }
+
void dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const override {
- OS << "AssignOrigin (Dest: ";
+ OS << "OriginFlow (Dest: ";
OM.dump(getDestOriginID(), OS);
OS << ", Src: ";
OM.dump(getSrcOriginID(), OS);
@@ -353,6 +357,23 @@ class UseFact : public Fact {
}
};
+class KillOriginFact : public Fact {
+ OriginID OID;
+
+public:
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::KillOrigin;
+ }
+ KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {}
+ OriginID getOriginID() const { return OID; }
+
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override {
+ OS << "KillOrigin (";
+ OM.dump(getOriginID(), OS);
+ OS << ")\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 {
@@ -454,7 +475,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
if (const auto *VD = dyn_cast<VarDecl>(D))
if (hasOrigin(VD))
if (const Expr *InitExpr = VD->getInit())
- addAssignOriginFact(*VD, *InitExpr);
+ addOriginFlowFact(*VD, *InitExpr);
}
void VisitDeclRefExpr(const DeclRefExpr *DRE) {
@@ -492,9 +513,23 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
// The argument is the implicit object itself.
handleFunctionCall(MCE, MCE->getMethodDecl(),
- {MCE->getImplicitObjectArgument()});
+ {MCE->getImplicitObjectArgument()},
+ /*IsGslConstruction=*/true);
}
- // FIXME: A more general VisitCallExpr could also be used here.
+ 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) {
@@ -508,7 +543,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
return;
// An ImplicitCastExpr node itself gets an origin, which flows from the
// origin of its sub-expression (after stripping its own parens/casts).
- addAssignOriginFact(*ICE, *ICE->getSubExpr());
+ addOriginFlowFact(*ICE, *ICE->getSubExpr());
}
void VisitUnaryOperator(const UnaryOperator *UO) {
@@ -522,7 +557,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
// 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.
- addAssignOriginFact(*UO, *SubExpr);
+ addOriginFlowFact(*UO, *SubExpr);
}
}
@@ -542,8 +577,15 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
}
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
- if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2)
+ // 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) {
@@ -552,7 +594,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
if (handleTestPoint(FCE))
return;
if (isGslPointerType(FCE->getType()))
- addAssignOriginFact(*FCE, *FCE->getSubExpr());
+ addOriginFlowFact(*FCE, *FCE->getSubExpr());
}
void VisitInitListExpr(const InitListExpr *ILE) {
@@ -561,7 +603,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
// 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)
- addAssignOriginFact(*ILE, *ILE->getInit(0));
+ addOriginFlowFact(*ILE, *ILE->getInit(0));
}
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) {
@@ -569,7 +611,7 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
return;
// A temporary object's origin is the same as the origin of the
// expression that initializes it.
- addAssignOriginFact(*MTE, *MTE->getSubExpr());
+ addOriginFlowFact(*MTE, *MTE->getSubExpr());
}
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
@@ -624,34 +666,68 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
if (CCE->getNumArgs() != 1)
return;
if (hasOrigin(CCE->getArg(0)))
- addAssignOriginFact(*CCE, *CCE->getArg(0));
+ addOriginFlowFact(*CCE, *CCE->getArg(0));
else
// This could be a new borrow.
handleFunctionCall(CCE, CCE->getConstructor(),
- {CCE->getArgs(), CCE->getNumArgs()});
+ {CCE->getArgs(), CCE->getNumArgs()},
+ /*IsGslConstruction=*/true);
+ }
+ static const FunctionDecl *
+ getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
+ return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
}
+ static const CXXMethodDecl *
+ getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
+ const FunctionDecl *FD = CMD;
+ return cast_if_present<CXXMethodDecl>(
+ getDeclWithMergedLifetimeBoundAttrs(FD));
+ }
+ static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
+ FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+ const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+ if (!TSI)
+ return false;
+ // Don't declare this variable in the second operand of the for-statement;
+ // GCC miscompiles that by ending its lifetime before evaluating the
+ // third operand. See gcc.gnu.org/PR86769.
+ AttributedTypeLoc ATL;
+ for (TypeLoc TL = TSI->getTypeLoc();
+ (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
+ TL = ATL.getModifiedLoc()) {
+ if (ATL.getAttrAs<LifetimeBoundAttr>())
+ return true;
+ }
+ return false;
+ }
/// Checks if a call-like expression creates a borrow by passing a value to a
/// reference parameter, creating an IssueFact if it does.
void handleFunctionCall(const Expr *Call, const FunctionDecl *FD,
- ArrayRef<const Expr *> Args) {
- if (!FD)
+ ArrayRef<const Expr *> Args,
+ bool IsGslConstruction = false) {
+ // Ignore functions returning values with no origin.
+ if (!FD || !hasOrigin(Call))
return;
- // TODO: Handle more than one arguments.
- for (unsigned I = 0; I <= 0 /*Args.size()*/; ++I) {
- const Expr *ArgExpr = Args[I];
-
- // Propagate origins for CXX this.
- if (FD->isCXXClassMember() && I == 0) {
- addAssignOriginFact(*Call, *ArgExpr);
- continue;
- }
- // The parameter is a pointer, reference, or gsl::Pointer.
- // This is a borrow. We propagate the origin from the argument expression
- // at the call site to the parameter declaration in the callee.
- if (hasOrigin(ArgExpr))
- addAssignOriginFact(*Call, *ArgExpr);
- }
+ 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;
+ };
+ for (unsigned I = 0; I < Args.size(); ++I)
+ if (IsGslConstruction || IsArgLifetimeBound(I))
+ addOriginFlowFact(*Call, *Args[I]);
}
/// Creates a loan for the storage path of a given declaration reference.
@@ -668,11 +744,16 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
}
template <typename Destination, typename Source>
- void addAssignOriginFact(const Destination &D, const Source &S) {
+ void addOriginFlowFact(const Destination &D, const Source &S) {
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
OriginID SrcOID = FactMgr.getOriginMgr().get(S);
CurrentBlockFacts.push_back(
- FactMgr.createFact<AssignOriginFact>(DestOID, SrcOID));
+ FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID));
+ }
+
+ void killOrigin(const ValueDecl *D) {
+ OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(*D);
+ CurrentBlockFacts.push_back(FactMgr.createFact<KillOriginFact>(DestOID));
}
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
@@ -703,12 +784,12 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
if (const auto *DRE_LHS =
dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
markUseAsWrite(DRE_LHS);
- if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl()))
- // We are interested in assignments like `ptr1 = ptr2` or `ptr = &var`.
- // LHS must be a pointer/reference type that can be an origin. RHS must
- // also represent an origin (either another pointer/ref or an
- // address-of).
- addAssignOriginFact(*VD_LHS, *RHSExpr);
+ 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.
+ killOrigin(VD_LHS);
+ addOriginFlowFact(*VD_LHS, *RHSExpr);
+ }
}
}
@@ -882,8 +963,10 @@ class DataflowAnalysis {
return D->transfer(In, *F->getAs<IssueFact>());
case Fact::Kind::Expire:
return D->transfer(In, *F->getAs<ExpireFact>());
- case Fact::Kind::AssignOrigin:
- return D->transfer(In, *F->getAs<AssignOriginFact>());
+ case Fact::Kind::OriginFlow:
+ return D->transfer(In, *F->getAs<OriginFlowFact>());
+ case Fact::Kind::KillOrigin:
+ return D->transfer(In, *F->getAs<KillOriginFact>());
case Fact::Kind::ReturnOfOrigin:
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
case Fact::Kind::Use:
@@ -897,7 +980,8 @@ class DataflowAnalysis {
public:
Lattice transfer(Lattice In, const IssueFact &) { return In; }
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
- Lattice transfer(Lattice In, const AssignOriginFact &) { return In; }
+ Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
+ Lattice transfer(Lattice In, const KillOriginFact &) { 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; }
@@ -1049,14 +1133,27 @@ class LoanPropagationAnalysis
LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
}
- /// The destination origin's loan set is replaced by the source's.
- /// This implicitly "resets" the old loans of the destination.
- Lattice transfer(Lattice In, const AssignOriginFact &F) {
+ /// A flow from source to destination adds the source's loans to the
+ /// destination's, without clearing the destination's existing loans.
+ Lattice transfer(Lattice In, const OriginFlowFact &F) {
OriginID DestOID = F.getDestOriginID();
OriginID SrcOID = F.getSrcOriginID();
+
+ LoanSet DestLoans = getLoans(In, DestOID);
LoanSet SrcLoans = getLoans(In, SrcOID);
+ LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
+
return LoanPropagationLattice(
- OriginLoanMapFactory.add(In.Origins, DestOID, SrcLoans));
+ OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans));
+ }
+
+ /// Clears the loan set of the specified origin. This is used on the
+ /// left-hand side of an assignment to invalidate the variable's old lifetime.
+ Lattice transfer(Lattice In, const KillOriginFact &F) {
+ OriginID OID = F.getOriginID();
+ // Replace the origin's loan set with an empty set.
+ return LoanPropagationLattice(OriginLoanMapFactory.add(
+ In.Origins, OID, LoanSetFactory.getEmptySet()));
}
LoanSet getLoans(OriginID OID, ProgramPoint P) {
diff --git a/clang/test/Analysis/LifetimeSafety/benchmark.py b/clang/test/Analysis/LifetimeSafety/benchmark.py
index 2373f9984eecd..d2e5f0b2122a3 100644
--- a/clang/test/Analysis/LifetimeSafety/benchmark.py
+++ b/clang/test/Analysis/LifetimeSafety/benchmark.py
@@ -340,7 +340,7 @@ def run_single_test(
"name": "cycle",
"title": "Pointer Cycle in Loop",
"generator_func": generate_cpp_cycle_test,
- "n_values": [25, 50, 75, 100],
+ "n_values": [50, 75, 100, 200, 300],
},
{
"name": "merge",
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 7dac27506fb6b..910b2df73b2d5 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -12,12 +12,12 @@ MyObj* return_local_addr() {
MyObj x {10};
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
MyObj* p = &x;
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
return p;
// CHECK: Use ([[O_P]] (Decl: p), Read)
-// CHECK: AssignOrigin (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
+// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
// CHECK: ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
// CHECK: Expire ([[L_X]] (Path: x))
}
@@ -29,26 +29,26 @@ MyObj* assign_and_return_local_addr() {
MyObj y{20};
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr))
MyObj* ptr1 = &y;
-// CHECK: AssignOrigin (Dest: [[O_PTR1:[0-9]+]] (Decl: ptr1), Src: [[O_ADDR_Y]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_PTR1:[0-9]+]] (Decl: ptr1), Src: [[O_ADDR_Y]] (Expr: UnaryOperator))
MyObj* ptr2 = ptr1;
// CHECK: Use ([[O_PTR1]] (Decl: ptr1), Read)
-// CHECK: AssignOrigin (Dest: [[O_PTR1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
-// CHECK: AssignOrigin (Dest: [[O_PTR2:[0-9]+]] (Decl: ptr2), Src: [[O_PTR1_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_PTR1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
+// CHECK: OriginFlow (Dest: [[O_PTR2:[0-9]+]] (Decl: ptr2), Src: [[O_PTR1_RVAL]] (Expr: ImplicitCastExpr))
ptr2 = ptr1;
// CHECK: Use ([[O_PTR1]] (Decl: ptr1), Read)
-// CHECK: AssignOrigin (Dest: [[O_PTR1_RVAL_2:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
+// CHECK: OriginFlow (Dest: [[O_PTR1_RVAL_2:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
// CHECK: Use ({{[0-9]+}} (Decl: ptr2), Write)
-// CHECK: AssignOrigin (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR1_RVAL_2]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR1_RVAL_2]] (Expr: ImplicitCastExpr))
ptr2 = ptr2; // Self assignment.
// CHECK: Use ([[O_PTR2]] (Decl: ptr2), Read)
-// CHECK: AssignOrigin (Dest: [[O_PTR2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
+// CHECK: OriginFlow (Dest: [[O_PTR2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
// CHECK: Use ([[O_PTR2]] (Decl: ptr2), Write)
-// CHECK: AssignOrigin (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR2_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR2_RVAL]] (Expr: ImplicitCastExpr))
return ptr2;
// CHECK: Use ([[O_PTR2]] (Decl: ptr2), Read)
-// CHECK: AssignOrigin (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
+// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
// CHECK: ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
// CHECK: Expire ([[L_Y]] (Path: y))
}
@@ -70,9 +70,9 @@ void loan_expires_cpp() {
MyObj obj{1};
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_OBJ:[0-9]+]] (Path: obj), ToOrigin: [[O_DRE_OBJ:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr))
MyObj* pObj = &obj;
-// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: pObj), Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pObj), Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator))
// CHECK: Expire ([[L_OBJ]] (Path: obj))
}
@@ -83,9 +83,9 @@ void loan_expires_trivial() {
int trivial_obj = 1;
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj), ToOrigin: [[O_DRE_TRIVIAL:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
int* pTrivialObj = &trivial_obj;
-// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
// CHECK-NOT: Expire
// CHECK-NEXT: End of Block
// FIXME: Add check for Expire once trivial destructors are handled for expiration.
@@ -100,20 +100,20 @@ void conditional(bool condition) {
if (condition)
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator))
p = &a;
else
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_B:[0-9]+]] (Path: b), ToOrigin: [[O_DRE_B:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_B:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_B]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_B]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_B:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_B]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_B]] (Expr: UnaryOperator))
p = &b;
// CHECK: Block B{{[0-9]+}}:
int *q = p;
// CHECK: Use ([[O_P]] (Decl: p), Read)
-// CHECK: AssignOrigin (Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
-// CHECK: AssignOrigin (Dest: [[O_Q:[0-9]+]] (Decl: q), Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
+// CHECK: OriginFlow (Dest: [[O_Q:[0-9]+]] (Decl: q), Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr))
}
@@ -128,36 +128,36 @@ void pointers_in_a_cycle(bool condition) {
MyObj* p3 = &v3;
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_V1:[0-9]+]] (Path: v1), ToOrigin: [[O_DRE_V1:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_V1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V1]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P1:[0-9]+]] (Decl: p1), Src: [[O_ADDR_V1]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_V1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V1]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P1:[0-9]+]] (Decl: p1), Src: [[O_ADDR_V1]] (Expr: UnaryOperator))
// CHECK: Issue ([[L_V2:[0-9]+]] (Path: v2), ToOrigin: [[O_DRE_V2:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_V2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V2]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P2:[0-9]+]] (Decl: p2), Src: [[O_ADDR_V2]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_V2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_V2]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P2:[0-9]+]] (Decl: p2), Src: [[O_ADDR_V2]] (Expr: UnaryOperator))
// CHECK: Issue ([[L_V3:[0-9]+]] (Path: v3), ToOrigin: [[O_DRE_V3:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_V3:[0-g]+]] (Expr: UnaryOperator), Src: [[O_DRE_V3]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P3:[0-9]+]] (Decl: p3), Src: [[O_ADDR_V3]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_V3:[0-g]+]] (Expr: UnaryOperator), Src: [[O_DRE_V3]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P3:[0-9]+]] (Decl: p3), Src: [[O_ADDR_V3]] (Expr: UnaryOperator))
while (condition) {
// CHECK: Block B{{[0-9]+}}:
MyObj* temp = p1;
// CHECK: Use ([[O_P1]] (Decl: p1), Read)
-// CHECK: AssignOrigin (Dest: [[O_P1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P1]] (Decl: p1))
-// CHECK: AssignOrigin (Dest: [[O_TEMP:[0-9]+]] (Decl: temp), Src: [[O_P1_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_P1_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P1]] (Decl: p1))
+// CHECK: OriginFlow (Dest: [[O_TEMP:[0-9]+]] (Decl: temp), Src: [[O_P1_RVAL]] (Expr: ImplicitCastExpr))
p1 = p2;
// CHECK: Use ([[O_P2:[0-9]+]] (Decl: p2), Read)
-// CHECK: AssignOrigin (Dest: [[O_P2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P2]] (Decl: p2))
+// CHECK: OriginFlow (Dest: [[O_P2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P2]] (Decl: p2))
// CHECK: Use ({{[0-9]+}} (Decl: p1), Write)
-// CHECK: AssignOrigin (Dest: [[O_P1]] (Decl: p1), Src: [[O_P2_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_P1]] (Decl: p1), Src: [[O_P2_RVAL]] (Expr: ImplicitCastExpr))
p2 = p3;
// CHECK: Use ([[O_P3:[0-9]+]] (Decl: p3), Read)
-// CHECK: AssignOrigin (Dest: [[O_P3_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P3]] (Decl: p3))
+// CHECK: OriginFlow (Dest: [[O_P3_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P3]] (Decl: p3))
// CHECK: Use ({{[0-9]+}} (Decl: p2), Write)
-// CHECK: AssignOrigin (Dest: [[O_P2]] (Decl: p2), Src: [[O_P3_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_P2]] (Decl: p2), Src: [[O_P3_RVAL]] (Expr: ImplicitCastExpr))
p3 = temp;
// CHECK: Use ([[O_TEMP]] (Decl: temp), Read)
-// CHECK: AssignOrigin (Dest: [[O_TEMP_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_TEMP]] (Decl: temp))
+// CHECK: OriginFlow (Dest: [[O_TEMP_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_TEMP]] (Decl: temp))
// CHECK: Use ({{[0-9]+}} (Decl: p3), Write)
-// CHECK: AssignOrigin (Dest: [[O_P3]] (Decl: p3), Src: [[O_TEMP_RVAL]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_P3]] (Decl: p3), Src: [[O_TEMP_RVAL]] (Expr: ImplicitCastExpr))
}
}
@@ -168,13 +168,13 @@ void overwrite_origin() {
// CHECK: Block B{{[0-9]+}}:
MyObj* p = &s1;
// CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
p = &s2;
// CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
// CHECK: Expire ([[L_S2]] (Path: s2))
// CHECK: Expire ([[L_S1]] (Path: s1))
}
@@ -185,12 +185,12 @@ void reassign_to_null() {
// CHECK: Block B{{[0-9]+}}:
MyObj* p = &s1;
// CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
p = nullptr;
-// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: {{[0-9]+}} (Expr: CXXNullPtrLiteralExpr))
+// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: {{[0-9]+}} (Expr: CXXNullPtrLiteralExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
// CHECK: Expire ([[L_S1]] (Path: s1))
}
// FIXME: Have a better representation for nullptr than just an empty origin.
@@ -204,15 +204,15 @@ void reassign_in_if(bool condition) {
MyObj* p = &s1;
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
if (condition) {
// CHECK: Block B{{[0-9]+}}:
p = &s2;
// CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
}
// CHECK: Block B{{[0-9]+}}:
// CHECK: Expire ([[L_S2]] (Path: s2))
@@ -227,32 +227,32 @@ void assign_in_switch(int mode) {
MyObj s3;
MyObj* p = nullptr;
// CHECK: Block B{{[0-9]+}}:
-// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
switch (mode) {
case 1:
// CHECK-DAG: Block B{{[0-9]+}}:
p = &s1;
// CHECK-DAG: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK-DAG: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK-DAG: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
// CHECK-DAG: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK-DAG: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
+// CHECK-DAG: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
break;
case 2:
// CHECK-DAG: Block B{{[0-9]+}}:
p = &s2;
// CHECK-DAG: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK-DAG: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK-DAG: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
// CHECK-DAG: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK-DAG: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
+// CHECK-DAG: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
break;
default:
// CHECK: Block B{{[0-9]+}}:
p = &s3;
// CHECK: Issue ([[L_S3:[0-9]+]] (Path: s3), ToOrigin: [[O_DRE_S3:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S3:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S3]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S3:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S3]] (Expr: DeclRefExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S3]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S3]] (Expr: UnaryOperator))
break;
}
// CHECK: Block B{{[0-9]+}}:
@@ -265,16 +265,16 @@ void assign_in_switch(int mode) {
void loan_in_loop(bool condition) {
MyObj* p = nullptr;
// CHECK: Block B{{[0-9]+}}:
-// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
while (condition) {
MyObj inner;
// CHECK: Block B{{[0-9]+}}:
p = &inner;
// CHECK: Issue ([[L_INNER:[0-9]+]] (Path: inner), ToOrigin: [[O_DRE_INNER:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator))
// CHECK: Expire ([[L_INNER]] (Path: inner))
}
}
@@ -286,16 +286,17 @@ void loop_with_break(int count) {
MyObj* p = &s1;
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S1]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_S1]] (Expr: UnaryOperator))
for (int i = 0; i < count; ++i) {
if (i == 5) {
// CHECK: Block B{{[0-9]+}}:
p = &s2;
// CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
+// CHECK: KillOrigin ([[O_P]] (Decl: p))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
break;
}
}
@@ -308,22 +309,24 @@ void loop_with_break(int count) {
void nested_scopes() {
MyObj* p = nullptr;
// CHECK: Block B{{[0-9]+}}:
-// CHECK: AssignOrigin (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_NULLPTR:[0-9]+]] (Expr: CXXNullPtrLiteralExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
{
MyObj outer;
p = &outer;
// CHECK: Issue ([[L_OUTER:[0-9]+]] (Path: outer), ToOrigin: [[O_DRE_OUTER:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_OUTER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OUTER]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_OUTER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OUTER]] (Expr: DeclRefExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_OUTER]] (Expr: UnaryOperator))
+// CHECK: KillOrigin ([[O_P]] (Decl: p))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_OUTER]] (Expr: UnaryOperator))
{
MyObj inner;
p = &inner;
// CHECK: Issue ([[L_INNER:[0-9]+]] (Path: inner), ToOrigin: [[O_DRE_INNER:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator))
+// CHECK: KillOrigin ([[O_P]] (Decl: p))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator))
}
// CHECK: Expire ([[L_INNER]] (Path: inner))
}
@@ -336,14 +339,14 @@ void pointer_indirection() {
int *p = &a;
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_A]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_A]] (Expr: UnaryOperator))
int **pp = &p;
// Note: No facts are generated for &p because the subexpression is a pointer type,
// which is not yet supported by the origin model. This is expected.
int *q = *pp;
// CHECK: Use ([[O_PP:[0-9]+]] (Decl: pp), Read)
-// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: ImplicitCastExpr))
+// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: ImplicitCastExpr))
}
// CHECK-LABEL: Function: ternary_operator
@@ -360,7 +363,7 @@ void ternary_operator() {
// CHECK: Block B{{[0-9]+}}:
// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: p), Src: {{[0-9]+}} (Expr: ConditionalOperator))
+// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: p), Src: {{[0-9]+}} (Expr: ConditionalOperator))
}
// CHECK-LABEL: Function: test_use_facts
@@ -371,9 +374,9 @@ void test_use_facts() {
// CHECK: Block B{{[0-9]+}}:
p = &x;
// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: AssignOrigin (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
// CHECK: Use ([[O_P:[0-9]+]] (Decl: p), Write)
-// CHECK: AssignOrigin (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
(void)*p;
// CHECK: Use ([[O_P]] (Decl: p), Read)
usePointer(p);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index bc8a5f3f7150f..0cfd69b68ec55 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -371,3 +371,152 @@ void no_error_if_dangle_then_rescue_gsl() {
v = safe; // 'v' is "rescued" before use by reassigning to a valid object.
v.use(); // This is safe.
}
+
+
+//===----------------------------------------------------------------------===//
+// Lifetimebound Attribute Tests
+//===----------------------------------------------------------------------===//
+
+View Identity(View v [[clang::lifetimebound]]);
+View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]);
+MyObj* GetPointer(const MyObj& obj [[clang::lifetimebound]]);
+
+struct [[gsl::Pointer()]] LifetimeBoundView {
+ LifetimeBoundView();
+ LifetimeBoundView(const MyObj& obj [[clang::lifetimebound]]);
+ LifetimeBoundView pass() [[clang::lifetimebound]] { return *this; }
+ operator View() const [[clang::lifetimebound]];
+};
+
+void lifetimebound_simple_function() {
+ View v;
+ {
+ MyObj obj;
+ v = Identity(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note {{later used here}}
+}
+
+void lifetimebound_multiple_args_definite() {
+ View v;
+ {
+ MyObj obj1, obj2;
+ v = Choose(true,
+ obj1, // expected-warning {{object whose reference is captured does not live long enough}}
+ obj2); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ v.use(); // expected-note 2 {{later used here}}
+}
+
+void lifetimebound_multiple_args_potential(bool cond) {
+ MyObj safe;
+ View v = safe;
+ {
+ MyObj obj1;
+ if (cond) {
+ MyObj obj2;
+ v = Choose(true,
+ obj1, // expected-warning {{object whose reference is captured may not live long enough}}
+ obj2); // expected-warning {{object whose reference is captured may not live long enough}}
+ } // expected-note {{destroyed here}}
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note 2 {{later used here}}
+}
+
+View SelectFirst(View a [[clang::lifetimebound]], View b);
+void lifetimebound_mixed_args() {
+ View v;
+ {
+ MyObj obj1, obj2;
+ v = SelectFirst(obj1, // expected-warning {{object whose reference is captured does not live long enough}}
+ obj2);
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note {{later used here}}
+}
+
+void lifetimebound_member_function() {
+ LifetimeBoundView lbv, lbv2;
+ {
+ MyObj obj;
+ lbv = obj; // expected-warning {{object whose reference is captured does not live long enough}}
+ lbv2 = lbv.pass();
+ } // expected-note {{destroyed here}}
+ View v = lbv2; // expected-note {{later used here}}
+ v.use();
+}
+
+void lifetimebound_conversion_operator() {
+ View v;
+ {
+ MyObj obj;
+ LifetimeBoundView lbv = obj; // expected-warning {{object whose reference is captured does not live long enough}}
+ v = lbv; // Conversion operator is lifetimebound
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note {{later used here}}
+}
+
+void lifetimebound_chained_calls() {
+ View v;
+ {
+ MyObj obj;
+ v = Identity(Identity(Identity(obj))); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note {{later used here}}
+}
+
+void lifetimebound_with_pointers() {
+ MyObj* ptr;
+ {
+ MyObj obj;
+ ptr = GetPointer(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*ptr; // expected-note {{later used here}}
+}
+
+void lifetimebound_no_error_safe_usage() {
+ MyObj obj;
+ View v1 = Identity(obj); // No warning - obj lives long enough
+ View v2 = Choose(true, v1, Identity(obj)); // No warning - all args are safe
+ v2.use(); // Safe usage
+}
+
+void lifetimebound_partial_safety(bool cond) {
+ MyObj safe_obj;
+ View v = safe_obj;
+
+ if (cond) {
+ MyObj temp_obj;
+ v = Choose(true,
+ safe_obj,
+ temp_obj); // expected-warning {{object whose reference is captured may not live long enough}}
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note {{later used here}}
+}
+
+// FIXME: Creating reference from lifetimebound call doesn't propagate loans.
+const MyObj& GetObject(View v [[clang::lifetimebound]]);
+void lifetimebound_return_reference() {
+ View v;
+ const MyObj* ptr;
+ {
+ MyObj obj;
+ View temp_v = obj;
+ const MyObj& ref = GetObject(temp_v);
+ ptr = &ref;
+ }
+ (void)*ptr;
+}
+
+// FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types.
+struct LifetimeBoundCtor {
+ LifetimeBoundCtor();
+ LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
+};
+void lifetimebound_ctor() {
+ LifetimeBoundCtor v;
+ {
+ MyObj obj;
+ v = obj;
+ }
+ (void)v;
+}
\ No newline at end of file
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index bff5378c0a8a9..3821015f07fb1 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -68,6 +68,7 @@ class LifetimeTestRunner {
LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; }
ASTContext &getASTContext() { return *ASTCtx; }
+ AnalysisDeclContext &getAnalysisContext() { return *AnalysisCtx; }
ProgramPoint getProgramPoint(llvm::StringRef Annotation) {
auto It = AnnotationToPointMap.find(Annotation);
@@ -106,7 +107,7 @@ class LifetimeTestHelper {
std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
auto *VD = findDecl<VarDecl>(VarName);
if (!VD) {
- ADD_FAILURE() << "No VarDecl found for '" << VarName << "'";
+ ADD_FAILURE() << "Failed to find VarDecl for '" << VarName << "'";
return {};
}
std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD);
@@ -136,11 +137,20 @@ class LifetimeTestHelper {
private:
template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
auto &Ctx = Runner.getASTContext();
- auto Results = match(valueDecl(hasName(Name)).bind("v"), Ctx);
+ const auto *TargetFunc = Runner.getAnalysisContext().getDecl();
+ auto Results =
+ match(valueDecl(hasName(Name),
+ hasAncestor(functionDecl(equalsNode(TargetFunc))))
+ .bind("v"),
+ Ctx);
if (Results.empty()) {
ADD_FAILURE() << "Declaration '" << Name << "' not found in AST.";
return nullptr;
}
+ if (Results.size() > 1) {
+ ADD_FAILURE() << "Multiple declarations found for '" << Name << "'";
+ return nullptr;
+ }
return const_cast<DeclT *>(selectFirst<DeclT>("v", Results));
}
@@ -208,6 +218,19 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(),
ExpectedLIDs.end());
}
+ std::sort(ExpectedLoans.begin(), ExpectedLoans.end());
+ std::sort(ActualLoans.begin(), ActualLoans.end());
+ if (ExpectedLoans != ActualLoans) {
+ *result_listener << "Expected: ";
+ for (const auto &LoanID : ExpectedLoans) {
+ *result_listener << LoanID.Value << ", ";
+ }
+ *result_listener << "Actual: ";
+ for (const auto &LoanID : ActualLoans) {
+ *result_listener << LoanID.Value << ", ";
+ }
+ return false;
+ }
return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
ActualLoans, result_listener);
@@ -921,5 +944,235 @@ TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) {
EXPECT_THAT(Origin("y"), HasLoansTo({"yl"}, "p1"));
}
+TEST_F(LifetimeAnalysisTest, LifetimeboundSimple) {
+ SetupTest(R"(
+ View Identity(View v [[clang::lifetimebound]]);
+ void target() {
+ MyObj a, b;
+ View v1 = a;
+ POINT(p1);
+
+ View v2 = Identity(v1);
+ View v3 = Identity(b);
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+ // The origin of v2 should now contain the loan to 'o' from v1.
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunction) {
+ SetupTest(R"(
+ struct [[gsl::Pointer()]] MyView {
+ MyView(const MyObj& o) {}
+ MyView pass() [[clang::lifetimebound]] { return *this; }
+ };
+ void target() {
+ MyObj o;
+ MyView v1 = o;
+ POINT(p1);
+ MyView v2 = v1.pass();
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
+ // The call v1.pass() is bound to 'v1'. The origin of v2 should get the loans
+ // from v1.
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundMultipleArgs) {
+ SetupTest(R"(
+ View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]);
+ void target() {
+ MyObj o1, o2;
+ View v1 = o1;
+ View v2 = o2;
+ POINT(p1);
+
+ View v3 = Choose(true, v1, v2);
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p2"));
+ // v3 should have loans from both v1 and v2, demonstrating the union of
+ // loans.
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"o1", "o2"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundMixedArgs) {
+ SetupTest(R"(
+ View Choose(bool cond, View a [[clang::lifetimebound]], View b);
+ void target() {
+ MyObj o1, o2;
+ View v1 = o1;
+ View v2 = o2;
+ POINT(p1);
+
+ View v3 = Choose(true, v1, v2);
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p1"));
+ // v3 should only have loans from v1, as v2 is not lifetimebound.
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"o1"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundChainOfViews) {
+ SetupTest(R"(
+ View Identity(View v [[clang::lifetimebound]]);
+ View DoubleIdentity(View v [[clang::lifetimebound]]);
+
+ void target() {
+ MyObj obj;
+ View v1 = obj;
+ POINT(p1);
+ View v2 = DoubleIdentity(Identity(v1));
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"obj"}, "p1"));
+ // v2 should inherit the loan from v1 through the chain of calls.
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"obj"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundRawPointerParameter) {
+ SetupTest(R"(
+ View ViewFromPtr(const MyObj* p [[clang::lifetimebound]]);
+ MyObj* PtrFromPtr(const MyObj* p [[clang::lifetimebound]]);
+ MyObj* PtrFromView(View v [[clang::lifetimebound]]);
+
+ void target() {
+ MyObj a;
+ View v = ViewFromPtr(&a);
+ POINT(p1);
+
+ MyObj b;
+ MyObj* ptr1 = PtrFromPtr(&b);
+ MyObj* ptr2 = PtrFromPtr(PtrFromPtr(PtrFromPtr(ptr1)));
+ POINT(p2);
+
+ MyObj c;
+ View v2 = ViewFromPtr(PtrFromView(c));
+ POINT(p3);
+ }
+ )");
+ EXPECT_THAT(Origin("v"), HasLoansTo({"a"}, "p1"));
+ EXPECT_THAT(Origin("ptr1"), HasLoansTo({"b"}, "p2"));
+ EXPECT_THAT(Origin("ptr2"), HasLoansTo({"b"}, "p2"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"c"}, "p3"));
+}
+
+// FIXME: This can be controversial and may be revisited in the future.
+TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefViewParameter) {
+ SetupTest(R"(
+ View Identity(const View& v [[clang::lifetimebound]]);
+ void target() {
+ MyObj o;
+ View v1 = o;
+ View v2 = Identity(v1);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefObjParam) {
+ SetupTest(R"(
+ View Identity(const MyObj& o [[clang::lifetimebound]]);
+ void target() {
+ MyObj a;
+ View v1 = Identity(a);
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundReturnReference) {
+ SetupTest(R"(
+ const MyObj& Identity(View v [[clang::lifetimebound]]);
+ void target() {
+ MyObj a;
+ View v1 = a;
+ POINT(p1);
+
+ View v2 = Identity(v1);
+
+ const MyObj& b = Identity(v1);
+ View v3 = Identity(b);
+ POINT(p2);
+
+ MyObj c;
+ View v4 = Identity(c);
+ POINT(p3);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
+
+ // FIXME: Handle reference types. 'v3' should have loan to 'a' instead of 'b'.
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2"));
+
+ EXPECT_THAT(Origin("v4"), HasLoansTo({"c"}, "p3"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunction) {
+ SetupTest(R"(
+ template <typename T>
+ const T& Identity(T&& v [[clang::lifetimebound]]);
+ void target() {
+ MyObj a;
+ View v1 = Identity(a);
+ POINT(p1);
+
+ View v2 = Identity(v1);
+ const View& v3 = Identity(v1);
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateClass) {
+ SetupTest(R"(
+ template<typename T>
+ struct [[gsl::Pointer()]] MyTemplateView {
+ MyTemplateView(const T& o) {}
+ MyTemplateView pass() [[clang::lifetimebound]] { return *this; }
+ };
+ void target() {
+ MyObj o;
+ MyTemplateView<MyObj> v1 = o;
+ POINT(p1);
+ MyTemplateView<MyObj> v2 = v1.pass();
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) {
+ SetupTest(R"(
+ struct MyOwner {
+ MyObj o;
+ operator View() const [[clang::lifetimebound]];
+ };
+
+ void target() {
+ MyOwner owner;
+ View v = owner;
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1"));
+}
} // anonymous namespace
} // namespace clang::lifetimes::internal
More information about the llvm-commits
mailing list