[clang] [LifetimeSafety] Implement multi-level origins (PR #168344)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Wed Nov 26 22:26:08 PST 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/168344
>From 82640a94c86635c939bc66a5e6bae35d4d3f146b Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 26 Nov 2025 08:33:33 +0000
Subject: [PATCH] Multi-origin changes
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 13 +-
.../Analyses/LifetimeSafety/FactsGenerator.h | 17 +-
.../Analyses/LifetimeSafety/LifetimeSafety.h | 4 +-
.../Analyses/LifetimeSafety/Origins.h | 122 +++++-
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 5 +-
.../LifetimeSafety/FactsGenerator.cpp | 303 +++++++++----
.../LifetimeSafety/LifetimeSafety.cpp | 34 +-
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 20 +-
.../LifetimeSafety/LoanPropagation.cpp | 3 +-
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 169 ++++---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 3 +
clang/test/Sema/warn-lifetime-safety.cpp | 414 ++++++++++++++++--
.../unittests/Analysis/LifetimeSafetyTest.cpp | 86 ++--
13 files changed, 927 insertions(+), 266 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index b5f7f8746186a..908d2a5b8cc76 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -155,7 +155,8 @@ class OriginEscapesFact : public Fact {
class UseFact : public Fact {
const Expr *UseExpr;
- OriginID OID;
+ // The origins of the expression being used.
+ llvm::SmallVector<OriginID, 1> OIDs;
// 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;
@@ -163,10 +164,10 @@ class UseFact : public Fact {
public:
static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
- UseFact(const Expr *UseExpr, OriginManager &OM)
- : Fact(Kind::Use), UseExpr(UseExpr), OID(OM.get(*UseExpr)) {}
+ UseFact(const Expr *UseExpr, llvm::ArrayRef<OriginID> OIDs)
+ : Fact(Kind::Use), UseExpr(UseExpr), OIDs(OIDs.begin(), OIDs.end()) {}
- OriginID getUsedOrigin() const { return OID; }
+ llvm::ArrayRef<OriginID> getUsedOrigins() const { return OIDs; }
const Expr *getUseExpr() const { return UseExpr; }
void markAsWritten() { IsWritten = true; }
bool isWritten() const { return IsWritten; }
@@ -194,8 +195,8 @@ class TestPointFact : public Fact {
class FactManager {
public:
- void init(const CFG &Cfg) {
- assert(BlockToFacts.empty() && "FactManager already initialized");
+ FactManager(const AnalysisDeclContext &AC, const CFG &Cfg)
+ : OriginMgr(AC.getASTContext()) {
BlockToFacts.resize(Cfg.getNumBlockIDs());
}
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 878cb90b685f9..939f421505463 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -50,6 +50,11 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
private:
+ OriginTree *getTree(const ValueDecl &D);
+ OriginTree *getTree(const Expr &E);
+
+ void flow(OriginTree *Dst, OriginTree *Src, bool Kill);
+
void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
@@ -64,26 +69,18 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
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));
+ flow(getTree(D), getTree(S), /*Kill=*/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));
+ flow(getTree(D), getTree(S), /*Kill=*/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);
- void handleAssignment(const Expr *LHSExpr, const Expr *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).
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index b34a7f18b5809..a8d6e2551aab5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -76,13 +76,13 @@ class LifetimeSafetyAnalysis {
return *LoanPropagation;
}
LiveOriginsAnalysis &getLiveOrigins() const { return *LiveOrigins; }
- FactManager &getFactManager() { return FactMgr; }
+ FactManager &getFactManager() { return *FactMgr; }
private:
AnalysisDeclContext &AC;
LifetimeSafetyReporter *Reporter;
LifetimeFactory Factory;
- FactManager FactMgr;
+ std::unique_ptr<FactManager> FactMgr;
std::unique_ptr<LiveOriginsAnalysis> LiveOrigins;
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
};
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 56b9010f41fa2..90124898b6ee0 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -16,6 +16,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/TypeBase.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
namespace clang::lifetimes::internal {
@@ -28,12 +29,10 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
/// 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
+///
+/// Each Origin corresponds to a single level of indirection. For complex types
+/// with multiple levels of indirection (e.g., `int**`), multiple Origins are
+/// organized into an OriginTree structure (see below).
struct Origin {
OriginID ID;
/// A pointer to the AST node that this origin represents. This union
@@ -41,8 +40,13 @@ struct Origin {
/// 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) {}
+ // Outermost type. TODO: Document.
+ QualType QT;
+
+ Origin(OriginID ID, const clang::ValueDecl *D, QualType QT)
+ : ID(ID), Ptr(D), QT(QT) {}
+ Origin(OriginID ID, const clang::Expr *E, QualType QT)
+ : ID(ID), Ptr(E), QT(QT) {}
const clang::ValueDecl *getDecl() const {
return Ptr.dyn_cast<const clang::ValueDecl *>();
@@ -52,28 +56,81 @@ struct Origin {
}
};
+/// A tree of origins representing levels of indirection for pointer-like types.
+///
+/// Each node in the tree contains an OriginID representing a level of
+/// indirection. The tree structure captures the multi-level nature of
+/// pointer and reference types in the lifetime analysis.
+///
+/// Examples:
+/// - For `int& x`, the tree has depth 2:
+/// * Root: origin for the reference storage itself (the lvalue `x`)
+/// * Pointee: origin for what `x` refers to
+///
+/// - For `int* p`, the tree has depth 2:
+/// * Root: origin for the pointer variable `p`
+/// * Pointee: origin for what `p` points to
+///
+/// - For `View v` (where View is gsl::Pointer), the tree has depth 2:
+/// * Root: origin for the view object itself
+/// * Pointee: origin for what the view refers to
+///
+/// - For `int** pp`, the tree has depth 3:
+/// * Root: origin for `pp` itself
+/// * Pointee: origin for `*pp` (what `pp` points to)
+/// * Pointee->Pointee: origin for `**pp` (what `*pp` points to)
+///
+/// The tree structure enables the analysis to track how loans flow through
+/// different levels of indirection when assignments and dereferences occur.
+struct OriginTree {
+ OriginID OID;
+ OriginTree *Pointee = nullptr;
+
+ OriginTree(OriginID OID) : OID(OID) {}
+
+ size_t getDepth() const {
+ size_t Depth = 1;
+ const OriginTree *T = this;
+ while (T->Pointee) {
+ T = T->Pointee;
+ Depth++;
+ }
+ return Depth;
+ }
+};
+
+bool hasOrigins(QualType QT);
+bool hasOrigins(const Expr *E);
+bool doesDeclHaveStorage(const ValueDecl *D);
+
/// 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);
- Origin &addOrigin(OriginID ID, const clang::Expr &E);
-
- // TODO: Mark this method as const once we remove the call to getOrCreate.
- OriginID get(const Expr &E);
-
- OriginID get(const ValueDecl &D);
-
- OriginID getOrCreate(const Expr &E);
+ explicit OriginManager(ASTContext &AST) : AST(AST) {}
+
+ /// Gets or creates the OriginTree for a given ValueDecl.
+ ///
+ /// Creates a tree structure mirroring the levels of indirection in the
+ /// declaration's type (e.g., `int** p` creates depth 2).
+ ///
+ /// \returns The OriginTree, or nullptr if the type is not pointer-like.
+ OriginTree *getOrCreateTree(const ValueDecl *D);
+
+ /// Gets or creates the OriginTree for a given Expr.
+ ///
+ /// Creates a tree based on the expression's type and value category:
+ /// - Lvalues get an implicit reference level (modeling addressability)
+ /// - Rvalues of non-pointer type return nullptr (no trackable origin)
+ /// - DeclRefExpr may reuse the underlying declaration's tree
+ ///
+ /// \returns The OriginTree, or nullptr for non-pointer rvalues.
+ OriginTree *getOrCreateTree(const Expr *E, size_t Depth = 0);
const Origin &getOrigin(OriginID ID) const;
llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }
- OriginID getOrCreate(const ValueDecl &D);
-
unsigned getNumOrigins() const { return NextOriginID.Value; }
void dump(OriginID OID, llvm::raw_ostream &OS) const;
@@ -81,12 +138,29 @@ class OriginManager {
private:
OriginID getNextOriginID() { return NextOriginID++; }
+ OriginTree *createNode(const ValueDecl *D, QualType QT) {
+ OriginID NewID = getNextOriginID();
+ AllOrigins.emplace_back(NewID, D, QT);
+ return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
+ }
+
+ OriginTree *createNode(const Expr *E, QualType QT) {
+ OriginID NewID = getNextOriginID();
+ AllOrigins.emplace_back(NewID, E, QT);
+ return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
+ }
+
+ template <typename T>
+ OriginTree *buildTreeForType(QualType QT, const T *Node);
+
+ ASTContext &AST;
OriginID NextOriginID{0};
- /// TODO(opt): Profile and evaluate the usefullness of small buffer
+ /// TODO(opt): Profile and evaluate the usefulness of small buffer
/// optimisation.
llvm::SmallVector<Origin> AllOrigins;
- llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
- llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
+ llvm::BumpPtrAllocator TreeAllocator;
+ llvm::DenseMap<const clang::ValueDecl *, OriginTree *> DeclToTree;
+ llvm::DenseMap<const clang::Expr *, OriginTree *> ExprToTree;
};
} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 0ae7111c489e8..99cefbd716c58 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -53,7 +53,10 @@ void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const {
OS << "Use (";
- OM.dump(getUsedOrigin(), OS);
+ for (OriginID OID : getUsedOrigins()) {
+ OM.dump(OID, OS);
+ OS << ", ";
+ }
OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
}
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 00870c3fd4086..ffb7e70204a2a 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -6,25 +6,39 @@
//
//===----------------------------------------------------------------------===//
+#include <cassert>
+#include <string>
+
+#include "clang/AST/OperationKinds.h"
#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/Signals.h"
#include "llvm/Support/TimeProfiler.h"
namespace clang::lifetimes::internal {
using llvm::isa_and_present;
-static bool isPointerType(QualType QT) {
- return QT->isPointerOrReferenceType() || isGslPointerType(QT);
+OriginTree *FactsGenerator::getTree(const ValueDecl &D) {
+ return FactMgr.getOriginMgr().getOrCreateTree(&D);
}
-// Check if a type has an origin.
-static bool hasOrigin(const Expr *E) {
- return E->isGLValue() || isPointerType(E->getType());
+OriginTree *FactsGenerator::getTree(const Expr &E) {
+ return FactMgr.getOriginMgr().getOrCreateTree(&E);
}
-static bool hasOrigin(const VarDecl *VD) {
- return isPointerType(VD->getType());
+void FactsGenerator::flow(OriginTree *Dst, OriginTree *Src, bool Kill) {
+ if (!Dst)
+ return;
+ assert(Dst->getDepth() == Src->getDepth() &&
+ "Trees must have the same shape");
+
+ while (Dst && Src) {
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<OriginFlowFact>(Dst->OID, Src->OID, Kill));
+ Dst = Dst->Pointee;
+ Src = Src->Pointee;
+ }
}
/// Creates a loan for the storage path of a given declaration reference.
@@ -64,29 +78,43 @@ void FactsGenerator::run() {
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);
+ if (const Expr *InitExpr = VD->getInit()) {
+ OriginTree *VDTree = getTree(*VD);
+ if (!VDTree)
+ continue;
+ OriginTree *InitTree = getTree(*InitExpr);
+ assert(InitTree && "VarDecl had origins but InitExpr did not");
+ // Special handling for rvalue references initialized with xvalues.
+ // For declarations like `Ranges&& r = std::move(ranges);`, the rvalue
+ // reference should directly refer to the object being moved from,
+ // rather than creating a new indirection level. We skip the outer
+ // reference level and flow the pointee origins directly.
+ if (VD->getType()->isRValueReferenceType() && InitExpr->isXValue()) {
+ flow(VDTree->Pointee, InitTree->Pointee, /*Kill=*/true);
+ continue;
+ }
+ flow(VDTree, InitTree, /*Kill=*/true);
+ }
}
void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
+ // Skip function references and PR values.
+ if (DRE->getFoundDecl()->isFunctionOrFunctionTemplate() || !DRE->isGLValue())
+ return;
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));
- }
+ // For pointer/view types, handleUse tracks all levels of indirection through
+ // the OriginTree structure.
+ //
+ // For non-pointer/non-reference types (e.g., `int x`), taking the address
+ // creates a borrow of the variable's storage. We issue a loan for this case.
+ if (doesDeclHaveStorage(DRE->getDecl())) {
+ const Loan *L = createLoan(FactMgr, DRE);
+ assert(L);
+ OriginTree *Tree = getTree(*DRE);
+ assert(Tree &&
+ "gl-value DRE of non-pointer type should have an origin tree");
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->ID, Tree->OID));
}
}
@@ -100,12 +128,14 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
// Specifically for conversion operators,
// like `std::string_view p = std::string{};`
- if (isGslPointerType(MCE->getType()) &&
- isa_and_present<CXXConversionDecl>(MCE->getCalleeDecl())) {
+ if (isa_and_present<CXXConversionDecl>(MCE->getCalleeDecl()) &&
+ isGslPointerType(MCE->getType()) &&
+ isGslOwnerType(MCE->getImplicitObjectArgument()->getType())) {
// The argument is the implicit object itself.
handleFunctionCall(MCE, MCE->getMethodDecl(),
{MCE->getImplicitObjectArgument()},
/*IsGslConstruction=*/true);
+ return;
}
if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
// Construct the argument list, with the implicit 'this' object as the
@@ -127,24 +157,46 @@ 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);
+ getTree(*N);
}
void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
- if (!hasOrigin(ICE))
+ OriginTree *Dest = getTree(*ICE);
+ if (!Dest)
+ return;
+ OriginTree *SrcTree = getTree(*ICE->getSubExpr());
+
+ if (ICE->getCastKind() == CK_LValueToRValue) {
+ // TODO: Decide what to do for x-values here.
+ if (!ICE->getSubExpr()->isLValue())
+ return;
+
+ assert(SrcTree && "LValue being cast to RValue has no origin tree");
+ // The result of an LValue-to-RValue cast on a reference-to-pointer like
+ // has the inner origin. Get rid of the outer origin.
+ flow(getTree(*ICE), SrcTree->Pointee, /*Kill=*/true);
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());
+ }
+ if (ICE->getCastKind() == CK_NullToPointer) {
+ getTree(*ICE);
+ // TODO: Flow into them a null origin.
+ return;
+ }
+ if (ICE->getCastKind() == CK_NoOp ||
+ ICE->getCastKind() == CK_ConstructorConversion ||
+ ICE->getCastKind() == CK_UserDefinedConversion)
+ flow(Dest, SrcTree, /*Kill=*/true);
+ if (ICE->getCastKind() == CK_FunctionToPointerDecay ||
+ ICE->getCastKind() == CK_BuiltinFnToFnPtr ||
+ ICE->getCastKind() == CK_ArrayToPointerDecay) {
+ // Ignore function-to-pointer decays.
+ return;
+ }
}
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
@@ -155,21 +207,35 @@ void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) {
void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
if (const Expr *RetExpr = RS->getRetValue()) {
- if (hasOrigin(RetExpr)) {
- OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
- EscapesInCurrentBlock.push_back(
- FactMgr.createFact<OriginEscapesFact>(OID, RetExpr));
- }
+ if (OriginTree *Tree = getTree(*RetExpr))
+ for (OriginTree *T = Tree; T; T = T->Pointee)
+ EscapesInCurrentBlock.push_back(
+ FactMgr.createFact<OriginEscapesFact>(T->OID, RetExpr));
}
}
void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
- if (BO->isAssignmentOp())
- handleAssignment(BO->getLHS(), BO->getRHS());
+ if (BO->isCompoundAssignmentOp())
+ return;
+ if (BO->isAssignmentOp()) {
+ const Expr *LHSExpr = BO->getLHS();
+ const Expr *RHSExpr = BO->getRHS();
+
+ if (const auto *DRE_LHS =
+ dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
+ OriginTree *LHSTree = getTree(*DRE_LHS);
+ OriginTree *RHSTree = getTree(*RHSExpr);
+ // TODO: Handle reference types.
+ markUseAsWrite(DRE_LHS);
+ // Kill the old loans of the destination origin and flow the new loans
+ // from the source origin.
+ flow(LHSTree->Pointee, RHSTree, /*Kill=*/true);
+ }
+ }
}
void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
- if (hasOrigin(CO)) {
+ if (hasOrigins(CO)) {
// Merge origins from both branches of the conditional operator.
// We kill to clear the initial state and merge both origins into it.
killAndFlowOrigin(*CO, *CO->getTrueExpr());
@@ -180,8 +246,26 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
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));
+ if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2) {
+
+ const Expr *LHSExpr = OCE->getArg(0);
+ const Expr *RHSExpr = OCE->getArg(1);
+
+ if (const auto *DRE_LHS =
+ dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
+ OriginTree *LHSTree = getTree(*DRE_LHS);
+ OriginTree *RHSTree = getTree(*RHSExpr);
+
+ // TODO: Doc why.
+ // Construction of GSL! View &(const View &).
+ if (RHSExpr->isGLValue())
+ RHSTree = RHSTree->Pointee;
+ // TODO: Handle reference types.
+ markUseAsWrite(DRE_LHS);
+ // Kill the old loans of the destination origin and flow the new loans
+ // from the source origin.
+ flow(LHSTree->Pointee, RHSTree, /*Kill=*/true);
+ }
return;
}
handleFunctionCall(OCE, OCE->getDirectCallee(),
@@ -200,7 +284,7 @@ void FactsGenerator::VisitCXXFunctionalCastExpr(
}
void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
- if (!hasOrigin(ILE))
+ if (!hasOrigins(ILE))
return;
// For list initialization with a single element, like `View{...}`, the
// origin of the list itself is the origin of its single element.
@@ -210,11 +294,24 @@ void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
void FactsGenerator::VisitMaterializeTemporaryExpr(
const MaterializeTemporaryExpr *MTE) {
- if (!hasOrigin(MTE))
+ OriginTree *MTETree = getTree(*MTE);
+ OriginTree *SubExprTree = getTree(*MTE->getSubExpr());
+ if (!MTETree)
return;
- // A temporary object's origin is the same as the origin of the
- // expression that initializes it.
- killAndFlowOrigin(*MTE, *MTE->getSubExpr());
+ if (MTE->isGLValue()) {
+ assert(!SubExprTree ||
+ MTETree->getDepth() == SubExprTree->getDepth() + 1 && "todo doc.");
+ // Issue a loan to the MTE.
+ // const Loan *L = createLoan(FactMgr, MTE);
+ // CurrentBlockFacts.push_back(
+ // FactMgr.createFact<IssueFact>(L->ID, MTETree->OID));
+ if (SubExprTree)
+ flow(MTETree->Pointee, SubExprTree, /*Kill=*/true);
+ } else {
+ assert(MTE->isXValue());
+ flow(MTETree, SubExprTree, /*Kill=*/true);
+ }
+ // TODO: MTE top level origin should contain a loan to the MTE itself.
}
void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
@@ -237,13 +334,24 @@ 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
+
+ if (isGslPointerType(CCE->getArg(0)->getType())) {
+ OriginTree *ArgTree = getTree(*CCE->getArg(0));
+ assert(ArgTree && "GSL pointer argument should have an origin tree");
+ // GSL pointer is constructed from another gsl pointer.
+ // Example:
+ // View(View v);
+ // View(const View &v);
+ if (ArgTree->getDepth() == 2)
+ ArgTree = ArgTree->Pointee;
+ flow(getTree(*CCE), ArgTree, /*Kill=*/true);
+ } else {
// This could be a new borrow.
+ // TODO: Add code example here.
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
@@ -254,8 +362,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
const FunctionDecl *FD,
ArrayRef<const Expr *> Args,
bool IsGslConstruction) {
+ OriginTree *CallTree = getTree(*Call);
// Ignore functions returning values with no origin.
- if (!FD || !hasOrigin(Call))
+ if (!FD || !CallTree)
return;
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
const ParmVarDecl *PVD = nullptr;
@@ -268,22 +377,41 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
// For explicit arguments, find the corresponding parameter
// declaration.
PVD = Method->getParamDecl(I - 1);
- } else if (I < FD->getNumParams())
+ } 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]);
+ bool KillSrc = true;
+ for (unsigned I = 0; I < Args.size(); ++I) {
+ OriginTree *ArgTree = getTree(*Args[I]);
+ if (!ArgTree)
+ continue;
+ if (IsGslConstruction) {
+ // TODO: document with code example.
+ // std::string_view(const std::string_view& from)
+ if (isGslPointerType(Args[I]->getType()) && Args[I]->isGLValue()) {
+ assert(ArgTree->getDepth() >= 2);
+ ArgTree = ArgTree->Pointee;
+ }
+ if (isGslOwnerType(Args[I]->getType())) {
+ // GSL construction creates a view that borrows from arguments.
+ // This implies flowing origins through the tree structure.
+ flow(CallTree, ArgTree, KillSrc);
+ KillSrc = false;
+ }
+ } else if (IsArgLifetimeBound(I)) {
+ // Lifetimebound on a non-GSL-ctor function means the returned
+ // pointer/reference itself must not outlive the arguments. This
+ // only constraints the top-level origin.
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ CallTree->OID, ArgTree->OID, KillSrc));
+ KillSrc = false;
}
+ }
}
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
@@ -307,39 +435,34 @@ bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) {
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, FactMgr.getOriginMgr());
- CurrentBlockFacts.push_back(UF);
- assert(!UseFacts.contains(DRE));
- UseFacts[DRE] = UF;
+ OriginTree *Tree = getTree(*DRE);
+ if (!Tree)
+ return;
+ // Remove the outer layer of origin which borrows from the decl directly. This
+ // is a use of the underlying decl.
+ Tree = Tree->Pointee;
+ // Skip if there is no inner origin (e.g., when it is not a pointer type).
+ if (!Tree)
+ return;
+ llvm::SmallVector<OriginID, 1> UsedOrigins;
+ OriginTree *T = Tree;
+ while (T) {
+ UsedOrigins.push_back(T->OID);
+ T = T->Pointee;
}
+ UseFact *UF = FactMgr.createFact<UseFact>(DRE, UsedOrigins);
+ 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();
+ if (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
index a51ba4280f284..50111f8ef1fba 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
@@ -31,6 +31,19 @@
namespace clang::lifetimes {
namespace internal {
+static void DebugOnlyFunction(AnalysisDeclContext &AC, const CFG &Cfg,
+ FactManager &FactMgr) {
+ std::string Name;
+ if (const Decl *D = AC.getDecl()) {
+ if (const auto *ND = dyn_cast<NamedDecl>(D))
+ Name = ND->getQualifiedNameAsString();
+ };
+ DEBUG_WITH_TYPE(Name.c_str(), AC.getDecl()->dumpColor());
+ DEBUG_WITH_TYPE(Name.c_str(), Cfg.dump(AC.getASTContext().getLangOpts(),
+ /*ShowColors=*/true));
+ DEBUG_WITH_TYPE(Name.c_str(), FactMgr.dump(Cfg, AC));
+}
+
LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
LifetimeSafetyReporter *Reporter)
: AC(AC), Reporter(Reporter) {}
@@ -41,11 +54,18 @@ void LifetimeSafetyAnalysis::run() {
const CFG &Cfg = *AC.getCFG();
DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
/*ShowColors=*/true));
- FactMgr.init(Cfg);
- FactsGenerator FactGen(FactMgr, AC);
+ FactMgr = std::make_unique<FactManager>(AC, Cfg);
+
+ FactsGenerator FactGen(*FactMgr, AC);
FactGen.run();
- DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
+
+ DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC));
+
+ // Debug print facts for a specific function using
+ // -debug-only=EnableFilterByFunctionName,YourFunctionNameFoo
+ DEBUG_WITH_TYPE("EnableFilterByFunctionName",
+ DebugOnlyFunction(AC, Cfg, *FactMgr));
/// TODO(opt): Consider optimizing individual blocks before running the
/// dataflow analysis.
@@ -59,14 +79,14 @@ void LifetimeSafetyAnalysis::run() {
/// 3. Collapse ExpireFacts belonging to same source location into a single
/// Fact.
LoanPropagation = std::make_unique<LoanPropagationAnalysis>(
- Cfg, AC, FactMgr, Factory.OriginMapFactory, Factory.LoanSetFactory);
+ Cfg, AC, *FactMgr, Factory.OriginMapFactory, Factory.LoanSetFactory);
LiveOrigins = std::make_unique<LiveOriginsAnalysis>(
- Cfg, AC, FactMgr, Factory.LivenessMapFactory);
+ Cfg, AC, *FactMgr, Factory.LivenessMapFactory);
DEBUG_WITH_TYPE("LiveOrigins",
- LiveOrigins->dump(llvm::dbgs(), FactMgr.getTestPoints()));
+ LiveOrigins->dump(llvm::dbgs(), FactMgr->getTestPoints()));
- runLifetimeChecker(*LoanPropagation, *LiveOrigins, FactMgr, AC, Reporter);
+ runLifetimeChecker(*LoanPropagation, *LiveOrigins, *FactMgr, AC, Reporter);
}
} // namespace internal
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 57338122b4440..e167d21260b36 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -121,13 +121,19 @@ class AnalysisImpl
/// 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();
- // 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)));
+ Lattice Out = In;
+ for (OriginID OID : UF.getUsedOrigins()) {
+ // Write kills liveness.
+ if (UF.isWritten()) {
+ Out = Lattice(Factory.remove(Out.LiveOrigins, OID));
+ } else {
+ // Read makes origin live with definite confidence (dominates this
+ // point).
+ Out = Lattice(Factory.add(Out.LiveOrigins, OID,
+ LivenessInfo(&UF, LivenessKind::Must)));
+ }
+ }
+ return Out;
}
/// An escaping origin (e.g., via return) makes the origin live with definite
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index 23ce1b78dfde2..e86ea7e2731b0 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -59,7 +59,8 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
break;
}
case Fact::Kind::Use:
- CheckOrigin(F->getAs<UseFact>()->getUsedOrigin());
+ for (OriginID OID : F->getAs<UseFact>()->getUsedOrigins())
+ CheckOrigin(OID);
break;
case Fact::Kind::OriginEscapes:
case Fact::Kind::Expire:
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 0f2eaa94a5987..4b9e8c85b177c 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -7,70 +7,133 @@
//===----------------------------------------------------------------------===//
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/TypeBase.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.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 << ")";
+bool hasOrigins(QualType QT) {
+ return QT->isPointerOrReferenceType() || isGslPointerType(QT);
+}
+
+/// Determines if an expression has origins that need to be tracked.
+///
+/// An expression has origins if:
+/// - It's a glvalue (has addressable storage), OR
+/// - Its type is pointer-like (pointer, reference, or gsl::Pointer)
+///
+/// Examples:
+/// - `int x; x` : has origin (glvalue)
+/// - `int* p; p` : has 2 origins (1 for glvalue and 1 for pointer type)
+/// - `std::string_view{}` : has 1 origin (prvalue of pointer type)
+/// - `42` : no origin (prvalue of non-pointer type)
+/// - `x + y` : (where x, y are int) → no origin (prvalue of non-pointer type)
+bool hasOrigins(const Expr *E) {
+ return E->isGLValue() || hasOrigins(E->getType());
}
-Origin &OriginManager::addOrigin(OriginID ID, const clang::ValueDecl &D) {
- AllOrigins.emplace_back(ID, &D);
- return AllOrigins.back();
+/// Returns true if the declaration has its own storage that can be borrowed.
+///
+/// References generally have no storage - they are aliases to other storage.
+/// For example:
+/// int x; // has storage (can issue loans to x's storage)
+/// int& r = x; // no storage (r is an alias to x's storage)
+/// int* p; // has storage (the pointer variable p itself has storage)
+///
+/// TODO: Handle lifetime extension. References initialized by temporaries
+/// can have storage when the temporary's lifetime is extended:
+/// const int& r = 42; // temporary has storage, lifetime extended
+/// Foo&& f = Foo{}; // temporary has storage, lifetime extended
+/// Currently, this function returns false for all reference types.
+bool doesDeclHaveStorage(const ValueDecl *D) {
+ return !D->getType()->isReferenceType();
}
-Origin &OriginManager::addOrigin(OriginID ID, const clang::Expr &E) {
- AllOrigins.emplace_back(ID, &E);
- return AllOrigins.back();
+template <typename T>
+OriginTree *OriginManager::buildTreeForType(QualType QT, const T *Node) {
+ assert(hasOrigins(QT) && "buildTreeForType called for non-pointer type");
+ OriginTree *Root = createNode(Node, QT);
+ if (QT->isPointerOrReferenceType()) {
+ QualType PointeeTy = QT->getPointeeType();
+ // We recurse if the pointee type is pointer-like, to build the next
+ // level in the origin tree. E.g., for T*& / View&.
+ if (hasOrigins(PointeeTy))
+ Root->Pointee = buildTreeForType(PointeeTy, Node);
+ }
+ return Root;
}
-// TODO: Mark this method as const once we remove the call to getOrCreate.
-OriginID OriginManager::get(const Expr &E) {
- if (auto *ParenIgnored = E.IgnoreParens(); ParenIgnored != &E)
- return get(*ParenIgnored);
- auto It = ExprToOriginID.find(&E);
- if (It != ExprToOriginID.end())
+OriginTree *OriginManager::getOrCreateTree(const ValueDecl *D) {
+ if (!hasOrigins(D->getType()))
+ return nullptr;
+ auto It = DeclToTree.find(D);
+ if (It != DeclToTree.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);
+ return DeclToTree[D] = buildTreeForType(D->getType(), D);
}
-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);
+OriginTree *OriginManager::getOrCreateTree(const Expr *E, size_t Depth) {
+ if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E)
+ return getOrCreateTree(ParenIgnored);
- return It->second;
-}
+ if (!hasOrigins(E))
+ return nullptr;
-OriginID OriginManager::getOrCreate(const Expr &E) {
- auto It = ExprToOriginID.find(&E);
- if (It != ExprToOriginID.end())
+ auto It = ExprToTree.find(E);
+ if (It != ExprToTree.end())
return It->second;
- OriginID NewID = getNextOriginID();
- addOrigin(NewID, E);
- ExprToOriginID[&E] = NewID;
- return NewID;
+ QualType Type = E->getType();
+
+ // Special handling for DeclRefExpr to share origins with the underlying decl.
+ //
+ // For reference-typed declarations (e.g., `int& r`), the DeclRefExpr
+ // directly reuses the declaration's tree since references don't add an
+ // extra level of indirection at the expression level.
+ //
+ // For non-reference declarations (e.g., `int* p`), the DeclRefExpr is an
+ // lvalue (addressable), so we create an outer origin for the lvalue itself,
+ // with the pointee being the declaration's tree. This models taking the
+ // address: `&p` borrows the storage of `p`, not what `p` points to.
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ OriginTree *Root = nullptr;
+ if (doesDeclHaveStorage(DRE->getDecl())) {
+ Root = createNode(DRE, QualType{});
+ // This ensures origin sharing: multiple DeclRefExprs to the same
+ // declaration share the same underlying origins.
+ Root->Pointee = getOrCreateTree(DRE->getDecl());
+ } else {
+ Root = getOrCreateTree(DRE->getDecl());
+ }
+ return ExprToTree[E] = Root;
+ }
+ // If E is an lvalue , it refers to storage. We model this storage as the
+ // first level of origin tree, as if it were a reference, because l-values are
+ // addressable.
+ if (E->isGLValue() && !Type->isReferenceType())
+ Type = AST.getLValueReferenceType(Type);
+ return ExprToTree[E] = buildTreeForType(Type, E);
+}
+
+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();
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ if (const ValueDecl *VD = DRE->getDecl())
+ OS << "(Decl: " << VD->getNameAsString() << ")";
+ }
+ } else {
+ OS << "Unknown";
+ }
+ OS << ")";
}
const Origin &OriginManager::getOrigin(OriginID ID) const {
@@ -78,14 +141,4 @@ const Origin &OriginManager::getOrigin(OriginID ID) const {
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/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 43d2b9a829545..44e3503a3b2ac 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3015,6 +3015,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
.setAlwaysAdd(Stmt::ImplicitCastExprClass)
.setAlwaysAdd(Stmt::UnaryOperatorClass);
}
+ if (EnableLifetimeSafetyAnalysis) {
+ AC.getCFGBuildOptions().AddLifetime = true;
+ }
// Install the logical handler.
std::optional<LogicalErrorHandler> LEH;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 1191469e23df1..e62c3b69b040b 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1,9 +1,13 @@
// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -Wno-dangling -verify %s
-struct MyObj {
+struct View;
+
+struct [[gsl::Owner]] MyObj {
int id;
~MyObj() {} // Non-trivial destructor
MyObj operator+(MyObj);
+
+ View getView() const [[clang::lifetimebound]];
};
struct [[gsl::Pointer()]] View {
@@ -224,6 +228,16 @@ void potential_for_loop_use_after_loop_body(MyObj safe) {
(void)*p; // expected-note {{later used here}}
}
+void safe_for_loop_gsl() {
+ MyObj safe;
+ View v = safe;
+ for (int i = 0; i < 1; ++i) {
+ MyObj s;
+ v = s;
+ v.use();
+ }
+}
+
void potential_for_loop_gsl() {
MyObj safe;
View v = safe;
@@ -418,6 +432,16 @@ void trivial_class_uaf() {
(void)ptr; // expected-note {{later used here}}
}
+void small_scope_reference_var_no_error() {
+ MyObj safe;
+ View view;
+ {
+ const MyObj& ref = safe;
+ view = ref;
+ }
+ view.use();
+}
+
//===----------------------------------------------------------------------===//
// Basic Definite Use-After-Return (Return-Stack-Address) (-W...permissive)
// These are cases where the pointer is guaranteed to be dangling at the use site.
@@ -435,6 +459,21 @@ MyObj* direct_return() {
// expected-note at -1 {{returned here}}
}
+const MyObj& return_reference_to_param_no_error(const MyObj& in) {
+ return in;
+}
+
+const MyObj& return_reference_to_param_via_ref_no_error(const MyObj& in) {
+ const MyObj& ref = in;
+ return ref;
+}
+
+const MyObj* getPointer();
+const MyObj& return_reference_to_param_via_pointer_no_error() {
+ const MyObj& ref = *getPointer();
+ return ref;
+}
+
const MyObj* conditional_assign_unconditional_return(const MyObj& safe, bool c) {
MyObj s;
const MyObj* p = &safe;
@@ -539,11 +578,28 @@ int* return_pointer_to_parameter(int a) {
// expected-note at -1 {{returned here}}
}
-const int& return_reference_to_parameter(int a)
-{
- const int &b = a;
- return b; // expected-warning {{address of stack memory is returned later}}
- // expected-note at -1 {{returned here}}
+const int& return_reference_to_parameter(int a) {
+ const int &b = a; // expected-warning {{address of stack memory is returned later}}
+ return b; // expected-note {{returned here}}
+}
+int return_reference_to_parameter_no_error(int a) {
+ const int &b = a;
+ return b;
+}
+
+const int& reference_via_conditional(int a, int b, bool cond) {
+ const int &c = (cond ? ((a)) : (b)); // expected-warning 2 {{address of stack memory is returned later}}
+ return c; // expected-note 2 {{returned here}}
+}
+const int* return_pointer_to_parameter_via_reference(int a, int b, bool cond) {
+ const int &c = cond ? a : b; // expected-warning 2 {{address of stack memory is returned later}}
+ const int* d = &c;
+ return d; // expected-note 2 {{returned here}}
+}
+// FIXME: Dereference of a pointer does not track the reference.
+const int& return_pointer_to_parameter_via_reference_1(int a) {
+ const int* d = &a;
+ return *d;
}
const int& get_ref_to_local() {
@@ -552,6 +608,71 @@ const int& get_ref_to_local() {
// expected-note at -1 {{returned here}}
}
+void test_view_pointer() {
+ View* vp;
+ {
+ View v;
+ vp = &v; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ vp->use(); // expected-note {{later used here}}
+}
+
+void test_view_double_pointer() {
+ View** vpp;
+ {
+ View* vp = nullptr;
+ vpp = &vp; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (**vpp).use(); // expected-note {{later used here}}
+}
+
+struct PtrHolder {
+ int* ptr;
+ int* const& getRef() const [[clang::lifetimebound]] { return ptr; }
+};
+
+int* const& test_ref_to_ptr() {
+ PtrHolder a;
+ int *const &ref = a.getRef(); // expected-warning {{address of stack memory is returned later}}
+ return ref; // expected-note {{returned here}}
+}
+int* const test_ref_to_ptr_no_error() {
+ PtrHolder a;
+ int *const &ref = a.getRef();
+ return ref;
+}
+
+int** return_inner_ptr_addr(int*** ppp [[clang::lifetimebound]]);
+void test_lifetimebound_multi_level() {
+ int** result;
+ {
+ int* p = nullptr;
+ int** pp = &p;
+ int*** ppp = &pp; // expected-warning {{object whose reference is captured does not live long enough}}
+ result = return_inner_ptr_addr(ppp);
+ } // expected-note {{destroyed here}}
+ (void)**result; // expected-note {{used here}}
+}
+
+// FIXME: Assignment does not track the dereference of a pointer.
+void test_assign_through_double_ptr() {
+ int a = 1, b = 2;
+ int* p = &a;
+ int** pp = &p;
+ {
+ int c = 3;
+ *pp = &c;
+ }
+ (void)**pp;
+}
+
+int** test_ternary_double_ptr(bool cond) {
+ int a = 1, b = 2;
+ int* pa = &a; // expected-warning {{address of stack memory is returned later}}
+ int* pb = &b; // expected-warning {{address of stack memory is returned later}}
+ int** result = cond ? &pa : &pb; // expected-warning 2 {{address of stack memory is returned later}}
+ return result; // expected-note 4 {{returned here}}
+}
//===----------------------------------------------------------------------===//
// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
// These are cases where the diagnostic kind is determined by location
@@ -635,13 +756,6 @@ MyObj* Identity(MyObj* 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;
{
@@ -688,25 +802,34 @@ void lifetimebound_mixed_args() {
v.use(); // expected-note {{later used here}}
}
+struct LifetimeBoundMember {
+ LifetimeBoundMember();
+ View get() const [[clang::lifetimebound]];
+ operator View() const [[clang::lifetimebound]];
+};
+
void lifetimebound_member_function() {
- LifetimeBoundView lbv, lbv2;
+ View v;
{
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();
+ v = obj.getView(); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note {{later used here}}
}
+struct LifetimeBoundConversionView {
+ LifetimeBoundConversionView();
+ ~LifetimeBoundConversionView();
+ operator View() const [[clang::lifetimebound]];
+};
+
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}}
+ LifetimeBoundConversionView obj;
+ v = 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_chained_calls() {
@@ -747,17 +870,15 @@ void lifetimebound_partial_safety(bool cond) {
v.use(); // expected-note {{later used here}}
}
-// FIXME: Warning should be on the 'GetObject' call, not the assignment to 'ptr'.
-// The loan from the lifetimebound argument is not propagated to the call expression itself.
const MyObj& GetObject(View v [[clang::lifetimebound]]);
void lifetimebound_return_reference() {
View v;
const MyObj* ptr;
{
MyObj obj;
- View temp_v = obj;
+ View temp_v = obj; // expected-warning {{object whose reference is captured does not live long enough}}
const MyObj& ref = GetObject(temp_v);
- ptr = &ref; // expected-warning {{object whose reference is captured does not live long enough}}
+ ptr = &ref;
} // expected-note {{destroyed here}}
(void)*ptr; // expected-note {{later used here}}
}
@@ -767,6 +888,7 @@ struct LifetimeBoundCtor {
LifetimeBoundCtor();
LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
};
+
void lifetimebound_ctor() {
LifetimeBoundCtor v;
{
@@ -822,7 +944,7 @@ void lambda_uar_param() {
}
// FIXME: This should be detected. We see correct destructors but origin flow breaks somewhere.
-namespace VariadicTemplatedParamsUAR{
+namespace VariadicTemplatedParamsUAR {
template<typename... Args>
View Max(Args... args [[clang::lifetimebound]]);
@@ -834,7 +956,7 @@ View lifetimebound_return_of_variadic_param(Args... args) {
void test_variadic() {
lifetimebound_return_of_variadic_param(MyObj{1}, MyObj{2}, MyObj{3});
}
-}
+} // namespace VariadicTemplatedParamsUAR
// FIXME: Fails to diagnose UAF when a reference to a by-value param escapes via an out-param.
void uaf_from_by_value_param_failing(MyObj param, View* out_p) {
@@ -943,3 +1065,235 @@ void parentheses(bool cond) {
} // expected-note 4 {{destroyed here}}
(void)*p; // expected-note 4 {{later used here}}
}
+
+namespace GH162834 {
+template <class T>
+struct StatusOr {
+ ~StatusOr() {}
+ const T& value() const& [[clang::lifetimebound]] { return data; }
+
+ private:
+ T data;
+};
+
+StatusOr<View> getViewOr();
+StatusOr<MyObj> getStringOr();
+StatusOr<MyObj*> getPointerOr();
+
+void foo() {
+ View view;
+ {
+ StatusOr<View> view_or = getViewOr();
+ view = view_or.value();
+ }
+ (void)view;
+}
+
+void bar() {
+ MyObj* pointer;
+ {
+ StatusOr<MyObj*> pointer_or = getPointerOr();
+ pointer = pointer_or.value();
+ }
+ (void)*pointer;
+}
+
+void foobar() {
+ View view;
+ {
+ StatusOr<MyObj> string_or = getStringOr();
+ view = string_or. // expected-warning {{object whose reference is captured does not live long enough}}
+ value();
+ } // expected-note {{destroyed here}}
+ (void)view; // expected-note {{later used here}}
+}
+} // namespace GH162834
+
+namespace RangeBasedForLoop {
+struct MyObjStorage {
+ MyObj objs[1];
+ MyObjStorage() {}
+ ~MyObjStorage() {}
+ const MyObj *begin() const [[clang::lifetimebound]] { return objs; }
+ const MyObj *end() const { return objs + 1; }
+};
+
+// FIXME: Detect use-after-scope. Dereference pointer does not propagate the origins.
+void range_based_for_use_after_scope() {
+ View v;
+ {
+ MyObjStorage s;
+ for (const MyObj &o : s) {
+ v = o;
+ }
+ }
+ v.use();
+}
+// FIXME: Detect use-after-return. Dereference pointer does not propagate the origins.
+View range_based_for_use_after_return() {
+ MyObjStorage s;
+ for (const MyObj &o : s) {
+ return o;
+ }
+ return *s.begin();
+}
+
+void range_based_for_not_reference() {
+ View v;
+ {
+ MyObjStorage s;
+ for (MyObj o : s) { // expected-note {{destroyed here}}
+ v = o; // expected-warning {{object whose reference is captured may not live long enough}}
+ }
+ }
+ v.use(); // expected-note {{later used here}}
+}
+
+void range_based_for_no_error() {
+ View v;
+ MyObjStorage s;
+ for (const MyObj &o : s) {
+ v = o;
+ }
+ v.use();
+}
+
+} // namespace RangeBaseForLoop
+
+namespace structured_binding {
+struct Pair {
+ MyObj a;
+ MyObj b;
+ Pair() {}
+ ~Pair() {}
+};
+
+// FIXME: Detect this.
+void structured_binding_use_after_scope() {
+ View v;
+ {
+ Pair p;
+ auto &[a_ref, b_ref] = p;
+ v = a_ref;
+ }
+ v.use();
+}
+}
+
+namespace MaxFnLifetimeBound {
+
+template<class T>
+T&& MaxT(T&& a [[clang::lifetimebound]], T&& b [[clang::lifetimebound]]);
+
+const MyObj& call_max_with_obj() {
+ MyObj oa, ob;
+ return MaxT(oa, // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 2 {{returned here}}
+ ob); // expected-warning {{address of stack memory is returned later}}
+
+}
+
+MyObj* call_max_with_obj_error() {
+ MyObj oa, ob;
+ return &MaxT(oa, // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 2 {{returned here}}
+ ob); // expected-warning {{address of stack memory is returned later}}
+}
+
+const MyObj* call_max_with_ref_obj_error() {
+ MyObj oa, ob;
+ const MyObj& refa = oa; // expected-warning {{address of stack memory is returned later}}
+ const MyObj& refb = ob; // expected-warning {{address of stack memory is returned later}}
+ return &MaxT(refa, refb); // expected-note 2 {{returned here}}
+}
+const MyObj& call_max_with_ref_obj_return_ref_error() {
+ MyObj oa, ob;
+ const MyObj& refa = oa; // expected-warning {{address of stack memory is returned later}}
+ const MyObj& refb = ob; // expected-warning {{address of stack memory is returned later}}
+ return MaxT(refa, refb); // expected-note 2 {{returned here}}
+}
+
+MyObj call_max_with_obj_no_error() {
+ MyObj oa, ob;
+ return MaxT(oa, ob);
+}
+
+const MyObj& call_max_with_ref_obj_no_error(const MyObj& a, const MyObj& b) {
+ return MaxT(a, b);
+}
+
+const View& call_max_with_view_with_error() {
+ View va, vb;
+ return MaxT(va, // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 2 {{returned here}}
+ vb); // expected-warning {{address of stack memory is returned later}}
+}
+
+struct [[gsl::Pointer]] NonTrivialPointer { ~NonTrivialPointer(); };
+
+const NonTrivialPointer& call_max_with_non_trivial_view_with_error() {
+ NonTrivialPointer va, vb;
+ return MaxT(va, // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 2 {{returned here}}
+ vb); // expected-warning {{address of stack memory is returned later}}
+}
+
+namespace MultiPointerTypes {
+int** return_2p() {
+ int a = 1;
+ int* b = &a; // expected-warning {{address of stack memory is returned later}}
+ int** c = &b; // expected-warning {{address of stack memory is returned later}}
+ return c; // expected-note 2 {{returned here}}
+}
+
+int** return_2p_one_is_safe(int& a) {
+ int* b = &a;
+ int** c = &b; // expected-warning {{address of stack memory is returned later}}
+ return c; // expected-note {{returned here}}
+}
+
+int*** return_3p() {
+ int a = 1;
+ int* b = &a; // expected-warning {{address of stack memory is returned later}}
+ int** c = &b; // expected-warning {{address of stack memory is returned later}}
+ int*** d = &c; // expected-warning {{address of stack memory is returned later}}
+ return d; // expected-note 3 {{returned here}}
+}
+
+View** return_view_p() {
+ MyObj a;
+ View b = a; // expected-warning {{address of stack memory is returned later}}
+ View* c = &b; // expected-warning {{address of stack memory is returned later}}
+ View** d = &c; // expected-warning {{address of stack memory is returned later}}
+ return d; // expected-note 3 {{returned here}}
+}
+
+} // namespace MultiPointerTypes
+
+View call_max_with_view_without_error() {
+ View va, vb;
+ return MaxT(va, vb);
+}
+
+} // namespace StdMaxStyleLifetimeBound
+
+namespace CppCoverage {
+
+int getInt();
+
+void ReferenceParam(unsigned Value, unsigned &Ref) {
+ Value = getInt();
+ Ref = getInt();
+}
+
+inline void normalize(int &exponent, int &mantissa) {
+ const int shift = 1;
+ exponent -= shift;
+ mantissa <<= shift;
+}
+
+void add(int c, MyObj* node) {
+ MyObj* arr[10];
+ arr[4] = node;
+}
+} // namespace CppCoverage
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index a895475013c98..892100111c768 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -32,7 +32,7 @@ class LifetimeTestRunner {
std::string FullCode = R"(
#define POINT(name) void("__lifetime_test_point_" #name)
- struct MyObj { ~MyObj() {} int i; };
+ struct [[gsl::Owner]] MyObj { ~MyObj() {} int i; };
struct [[gsl::Pointer()]] View {
View(const MyObj&);
@@ -103,8 +103,14 @@ class LifetimeTestHelper {
// 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 const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr())
- .get(*VD);
+ OriginTree *Tree =
+ const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr())
+ .getOrCreateTree(VD);
+ if (!Tree) {
+ ADD_FAILURE() << "No origin tree found for Var '" << VarName << "'";
+ return std::nullopt;
+ }
+ return Tree->OID;
}
std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
@@ -826,7 +832,7 @@ TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) {
)");
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p1"));
- EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p1"));
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"v2"}, "p1"));
}
TEST_F(LifetimeAnalysisTest, GslPointerPropagation) {
@@ -879,7 +885,7 @@ TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) {
StringView() = default;
};
- struct String {
+ struct [[gsl::Owner]] String {
~String() {}
operator StringView() const;
};
@@ -915,24 +921,38 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundSimple) {
EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2"));
}
-TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunction) {
+TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunctionOfAView) {
SetupTest(R"(
struct [[gsl::Pointer()]] MyView {
MyView(const MyObj& o) {}
- MyView pass() [[clang::lifetimebound]] { return *this; }
+ //
+ MyView& pass() [[clang::lifetimebound]] { return *this; }
};
void target() {
MyObj o;
MyView v1 = o;
POINT(p1);
- MyView v2 = v1.pass();
+ 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"));
+ // The call v1.pass() is bound to 'v1'.
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"v1"}, "p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunction) {
+ SetupTest(R"(
+ struct LifetimeboundMember {
+ View get() [[clang::lifetimebound]];
+ };
+ void target() {
+ LifetimeboundMember o;
+ View v1 = o.get();
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
}
TEST_F(LifetimeAnalysisTest, LifetimeboundMultipleArgs) {
@@ -1019,7 +1039,6 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundRawPointerParameter) {
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]]);
@@ -1030,7 +1049,8 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefViewParameter) {
POINT(p1);
}
)");
- EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p1"));
+ EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"v1"}, "p1"));
}
TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefObjParam) {
@@ -1067,13 +1087,12 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundReturnReference) {
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("v3"), HasLoansTo({"a"}, "p2"));
EXPECT_THAT(Origin("v4"), HasLoansTo({"c"}, "p3"));
}
-TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunction) {
+TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunctionReturnRef) {
SetupTest(R"(
template <typename T>
const T& Identity(T&& v [[clang::lifetimebound]]);
@@ -1083,32 +1102,39 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunction) {
POINT(p1);
View v2 = Identity(v1);
- const View& v3 = Identity(v1);
+ const View& v3 = Identity(v2);
POINT(p2);
}
)");
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
- EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
- EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p2"));
+ EXPECT_THAT(Origin("v2"), HasLoansTo({}, "p2"));
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"v2"}, "p2"));
}
-TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateClass) {
+TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunctionReturnVal) {
SetupTest(R"(
- template<typename T>
- struct [[gsl::Pointer()]] MyTemplateView {
- MyTemplateView(const T& o) {}
- MyTemplateView pass() [[clang::lifetimebound]] { return *this; }
- };
+ template <typename T>
+ T Identity(const T& v [[clang::lifetimebound]]);
+
void target() {
- MyObj o;
- MyTemplateView<MyObj> v1 = o;
+ MyObj a;
+ // FIXME: Captures a reference to temporary MyObj returned by Identity.
+ View v1 = Identity(a);
POINT(p1);
- MyTemplateView<MyObj> v2 = v1.pass();
+
+ MyObj b;
+ View v2 = b;
+ View v3 = Identity(v2);
+ const View& v4 = Identity(v3);
POINT(p2);
}
)");
- EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
- EXPECT_THAT(Origin("v2"), HasLoansTo({"o"}, "p2"));
+ EXPECT_THAT(Origin("v1"), HasLoansTo({}, "p1"));
+
+ EXPECT_THAT(Origin("v2"), HasLoansTo({"b"}, "p2"));
+ EXPECT_THAT(Origin("v3"), HasLoansTo({"v2"}, "p2"));
+ // View temporary on RHS is lifetime-extended.
+ EXPECT_THAT(Origin("v4"), HasLoansTo({}, "p2"));
}
TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) {
More information about the cfe-commits
mailing list