[clang] [LifetimeSafety] Implement multi-level origins (PR #168344)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Tue Dec 16 22:51:09 PST 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/168344
>From 18f112af28e92cded4311bc9cc63dc47d7599dd4 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] Tree -> List
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 12 +-
.../Analyses/LifetimeSafety/FactsGenerator.h | 19 +-
.../Analyses/LifetimeSafety/LifetimeSafety.h | 4 +-
.../Analyses/LifetimeSafety/Origins.h | 128 ++++-
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 17 +-
.../LifetimeSafety/FactsGenerator.cpp | 332 +++++++----
.../LifetimeSafety/LifetimeSafety.cpp | 34 +-
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 22 +-
.../LifetimeSafety/LoanPropagation.cpp | 4 +-
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 185 ++++--
clang/lib/Sema/AnalysisBasedWarnings.cpp | 3 +
.../Sema/warn-lifetime-safety-dataflow.cpp | 526 +++++-------------
.../Sema/warn-lifetime-safety-suggestions.cpp | 53 +-
clang/test/Sema/warn-lifetime-safety.cpp | 415 +++++++++++++-
.../unittests/Analysis/LifetimeSafetyTest.cpp | 85 ++-
15 files changed, 1181 insertions(+), 658 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index b5f7f8746186a..a66925b7302ca 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -155,7 +155,7 @@ class OriginEscapesFact : public Fact {
class UseFact : public Fact {
const Expr *UseExpr;
- OriginID OID;
+ const OriginList *OList;
// 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 +163,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, const OriginList *OList)
+ : Fact(Kind::Use), UseExpr(UseExpr), OList(OList) {}
- OriginID getUsedOrigin() const { return OID; }
+ const OriginList *getUsedOrigins() const { return OList; }
const Expr *getUseExpr() const { return UseExpr; }
void markAsWritten() { IsWritten = true; }
bool isWritten() const { return IsWritten; }
@@ -194,8 +194,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 a1acd8615afdd..e255b04a12684 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -50,6 +50,13 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
private:
+ OriginList *getOriginsList(const ValueDecl &D);
+ OriginList *getOriginsList(const Expr &E);
+
+ void flow(OriginList *Dst, OriginList *Src, bool Kill);
+
+ void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);
+
void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
@@ -64,26 +71,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(getOriginsList(D), getOriginsList(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(getOriginsList(D), getOriginsList(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 31fae55f60486..2b3a220edc073 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -80,13 +80,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..d6246183a5ca7 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 OriginList structure (see below).
struct Origin {
OriginID ID;
/// A pointer to the AST node that this origin represents. This union
@@ -41,8 +40,19 @@ 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) {}
+ /// The type at this indirection level.
+ ///
+ /// For `int** pp`:
+ /// Root origin: QT = `int**` (what pp points to)
+ /// Pointee origin: QT = `int*` (what *pp points to)
+ ///
+ /// Null for synthetic lvalue origins (e.g., outer origin of DeclRefExpr).
+ const Type *Ty;
+
+ Origin(OriginID ID, const clang::ValueDecl *D, const Type *QT)
+ : ID(ID), Ptr(D), Ty(QT) {}
+ Origin(OriginID ID, const clang::Expr *E, const Type *QT)
+ : ID(ID), Ptr(E), Ty(QT) {}
const clang::ValueDecl *getDecl() const {
return Ptr.dyn_cast<const clang::ValueDecl *>();
@@ -52,28 +62,90 @@ struct Origin {
}
};
-/// Manages the creation, storage, and retrieval of origins for pointer-like
-/// variables and expressions.
-class OriginManager {
+/// A list of origins representing levels of indirection for pointer-like types.
+///
+/// Each node in the list contains an OriginID representing a level of
+/// indirection. The list structure captures the multi-level nature of
+/// pointer and reference types in the lifetime analysis.
+///
+/// Examples:
+/// - For `int& x`, the list has size 2:
+/// * Outer: origin for the reference storage itself (the lvalue `x`)
+/// * Inner: origin for what `x` refers to
+///
+/// - For `int* p`, the list has size 2:
+/// * Outer: origin for the pointer variable `p`
+/// * Inner: origin for what `p` points to
+///
+/// - For `View v` (where View is gsl::Pointer), the list has size 2:
+/// * Outer: origin for the view object itself
+/// * Inner: origin for what the view refers to
+///
+/// - For `int** pp`, the list has size 3:
+/// * Outer: origin for `pp` itself
+/// * Inner: origin for `*pp` (what `pp` points to)
+/// * Inner->Inner: origin for `**pp` (what `*pp` points to)
+///
+/// The list structure enables the analysis to track how loans flow through
+/// different levels of indirection when assignments and dereferences occur.
+class OriginList {
public:
- OriginManager() = default;
-
- Origin &addOrigin(OriginID ID, const clang::ValueDecl &D);
- Origin &addOrigin(OriginID ID, const clang::Expr &E);
+ OriginList(OriginID OID) : OuterOID(OID) {}
+
+ OriginList *peelOuterOrigin() const { return InnerList; }
+ OriginID getOuterOriginID() const { return OuterOID; }
+
+ void setInnerOriginList(OriginList *Inner) { InnerList = Inner; }
+
+ // Used for assertion checks only (to ensure origin lists have matching
+ // lengths).
+ size_t getLength() const {
+ size_t Length = 1;
+ const OriginList *T = this;
+ while (T->InnerList) {
+ T = T->InnerList;
+ Length++;
+ }
+ return Length;
+ }
- // TODO: Mark this method as const once we remove the call to getOrCreate.
- OriginID get(const Expr &E);
+private:
+ OriginID OuterOID;
+ OriginList *InnerList = nullptr;
+};
- OriginID get(const ValueDecl &D);
+bool hasOrigins(QualType QT);
+bool hasOrigins(const Expr *E);
+bool doesDeclHaveStorage(const ValueDecl *D);
- OriginID getOrCreate(const Expr &E);
+/// Manages the creation, storage, and retrieval of origins for pointer-like
+/// variables and expressions.
+class OriginManager {
+public:
+ explicit OriginManager(ASTContext &AST) : AST(AST) {}
+
+ /// Gets or creates the OriginList for a given ValueDecl.
+ ///
+ /// Creates a list structure mirroring the levels of indirection in the
+ /// declaration's type (e.g., `int** p` creates list of size 2).
+ ///
+ /// \returns The OriginList, or nullptr if the type is not pointer-like.
+ OriginList *getOrCreateList(const ValueDecl *D);
+
+ /// Gets or creates the OriginList for a given Expr.
+ ///
+ /// Creates a list 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 list
+ ///
+ /// \returns The OriginList, or nullptr for non-pointer rvalues.
+ OriginList *getOrCreateList(const Expr *E);
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 +153,20 @@ class OriginManager {
private:
OriginID getNextOriginID() { return NextOriginID++; }
+ OriginList *createNode(const ValueDecl *D, QualType QT);
+ OriginList *createNode(const Expr *E, QualType QT);
+
+ template <typename T>
+ OriginList *buildListForType(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 ListAllocator;
+ llvm::DenseMap<const clang::ValueDecl *, OriginList *> DeclToList;
+ llvm::DenseMap<const clang::Expr *, OriginList *> ExprToList;
};
} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 68317318ff4e2..2673ce5ba354b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -35,12 +35,14 @@ void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const {
- OS << "OriginFlow (Dest: ";
+ OS << "OriginFlow: \n";
+ OS << "\tDest: ";
OM.dump(getDestOriginID(), OS);
- OS << ", Src: ";
+ OS << "\n";
+ OS << "\tSrc: ";
OM.dump(getSrcOriginID(), OS);
OS << (getKillDest() ? "" : ", Merge");
- OS << ")\n";
+ OS << "\n";
}
void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
@@ -53,7 +55,14 @@ 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);
+ size_t NumUsedOrigins = getUsedOrigins()->getLength();
+ size_t I = 0;
+ for (const OriginList *Cur = getUsedOrigins(); Cur;
+ Cur = Cur->peelOuterOrigin(), ++I) {
+ OM.dump(Cur->getOuterOriginID(), OS);
+ if (I < NumUsedOrigins - 1)
+ OS << ", ";
+ }
OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
}
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2f270b03996f2..6a213a71afe12 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -6,25 +6,55 @@
//
//===----------------------------------------------------------------------===//
+#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/LifetimeSafety/Origins.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);
+OriginList *FactsGenerator::getOriginsList(const ValueDecl &D) {
+ return FactMgr.getOriginMgr().getOrCreateList(&D);
}
-// Check if a type has an origin.
-static bool hasOrigin(const Expr *E) {
- return E->isGLValue() || isPointerType(E->getType());
+OriginList *FactsGenerator::getOriginsList(const Expr &E) {
+ return FactMgr.getOriginMgr().getOrCreateList(&E);
}
-static bool hasOrigin(const VarDecl *VD) {
- return isPointerType(VD->getType());
+/// Propagates origin information from Src to Dst through all levels of
+/// indirection, creating OriginFlowFacts at each level.
+///
+/// This function enforces a critical type-safety invariant: both lists must
+/// have the same shape (same depth/structure). This invariant ensures that
+/// origins flow only between compatible types during expression evaluation.
+///
+/// Examples:
+/// - `int* p = &x;` flows origins from `&x` (depth 1) to `p` (depth 1)
+/// - `int** pp = &p;` flows origins from `&p` (depth 2) to `pp` (depth 2)
+/// * Level 1: pp <- p's address
+/// * Level 2: (*pp) <- what p points to (i.e., &x)
+/// - `View v = obj;` flows origins from `obj` (depth 1) to `v` (depth 1)
+void FactsGenerator::flow(OriginList *Dst, OriginList *Src, bool Kill) {
+ if (!Dst)
+ return;
+ assert(Src &&
+ "Dst is non-null but Src is null. List must have the same length");
+ assert(Dst->getLength() == Src->getLength() &&
+ "Lists must have the same length");
+
+ while (Dst && Src) {
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ Dst->getOuterOriginID(), Src->getOuterOriginID(), Kill));
+ Dst = Dst->peelOuterOrigin();
+ Src = Src->peelOuterOrigin();
+ }
}
/// Creates a loan for the storage path of a given declaration reference.
@@ -67,32 +97,56 @@ void FactsGenerator::run() {
}
}
+/// Simulates LValueToRValue conversion by peeling the outer lvalue origin
+/// if the expression is a GLValue. For pointer/view GLValues, this strips
+/// the origin representing the storage location to get the origins of the
+/// pointed-to value.
+///
+/// Example: For `View& v`, returns the origin of what v points to, not v's
+/// storage.
+static OriginList *getRValueOrigins(const Expr *E, OriginList *List) {
+ if (!List)
+ return nullptr;
+ return E->isGLValue() ? List->peelOuterOrigin() : List;
+}
+
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()) {
+ OriginList *VDList = getOriginsList(*VD);
+ if (!VDList)
+ continue;
+ OriginList *InitList = getOriginsList(*InitExpr);
+ assert(InitList && "VarDecl had origins but InitExpr did not");
+ flow(VDList, InitList, /*Kill=*/true);
+ }
}
void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
+ // Skip function references as their lifetimes are not interesting. Skip non
+ // GLValues (like EnumConstants).
+ 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->getID(), ExprOID));
- }
+ // For all declarations with storage (non-references), we issue a loan
+ // representing the borrow of the variable's storage itself.
+ //
+ // Examples:
+ // - `int x; x` issues loan to x's storage
+ // - `int* p; p` issues loan to p's storage (the pointer variable)
+ // - `View v; v` issues loan to v's storage (the view object)
+ // - `int& r = x; r` issues no loan (r has no storage, it's an alias to x)
+ if (doesDeclHaveStorage(DRE->getDecl())) {
+ const Loan *L = createLoan(FactMgr, DRE);
+ assert(L);
+ OriginList *List = getOriginsList(*DRE);
+ assert(List &&
+ "gl-value DRE of non-pointer type should have an origin list");
+ // This loan specifically tracks borrowing the variable's storage location
+ // itself and is issued to outermost origin (List->OID).
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
}
}
@@ -107,11 +161,13 @@ 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())) {
+ isa_and_present<CXXConversionDecl>(MCE->getCalleeDecl()) &&
+ 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
@@ -133,24 +189,51 @@ 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);
+ getOriginsList(*N);
}
void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
- if (!hasOrigin(ICE))
+ OriginList *Dest = getOriginsList(*ICE);
+ if (!Dest)
+ return;
+ OriginList *SrcList = getOriginsList(*ICE->getSubExpr());
+
+ switch (ICE->getCastKind()) {
+ case CK_LValueToRValue:
+ // TODO: Decide what to do for x-values here.
+ if (!ICE->getSubExpr()->isLValue())
+ return;
+
+ assert(SrcList && "LValue being cast to RValue has no origin list");
+ // The result of an LValue-to-RValue cast on a pointer lvalue (like `q` in
+ // `int *p, *q; p = q;`) should propagate the inner origin (what the pointer
+ // points to), not the outer origin (the pointer's storage location). Strip
+ // the outer lvalue origin.
+ flow(getOriginsList(*ICE), getRValueOrigins(ICE->getSubExpr(), SrcList),
+ /*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());
+ case CK_NullToPointer:
+ getOriginsList(*ICE);
+ // TODO: Flow into them a null origin.
+ return;
+ case CK_NoOp:
+ case CK_ConstructorConversion:
+ case CK_UserDefinedConversion:
+ flow(Dest, SrcList, /*Kill=*/true);
+ return;
+ case CK_FunctionToPointerDecay:
+ case CK_BuiltinFnToFnPtr:
+ case CK_ArrayToPointerDecay:
+ // Ignore function-to-pointer decays.
+ return;
+ default:
+ 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
@@ -161,21 +244,47 @@ 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 (OriginList *List = getOriginsList(*RetExpr))
+ for (OriginList *L = List; L != nullptr; L = L->peelOuterOrigin())
+ EscapesInCurrentBlock.push_back(FactMgr.createFact<OriginEscapesFact>(
+ L->getOuterOriginID(), RetExpr));
+ }
+}
+
+void FactsGenerator::handleAssignment(const Expr *LHSExpr,
+ const Expr *RHSExpr) {
+ if (const auto *DRE_LHS =
+ dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
+ OriginList *LHSList = getOriginsList(*DRE_LHS);
+ assert(LHSList && "LHS is a DRE and should have an origin list");
+ OriginList *RHSList = getOriginsList(*RHSExpr);
+
+ // For operator= with reference parameters (e.g.,
+ // `View& operator=(const View&)`), the RHS argument stays an lvalue,
+ // unlike built-in assignment where LValueToRValue cast strips the outer
+ // lvalue origin. Strip it manually to get the actual value origins being
+ // assigned.
+ RHSList = getRValueOrigins(RHSExpr, RHSList);
+
+ markUseAsWrite(DRE_LHS);
+ // Kill the old loans of the destination origin and flow the new loans
+ // from the source origin.
+ flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
}
}
void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
+ // TODO: Handle pointer arithmetic (e.g., `p + 1` or `1 + p`) where the
+ // result should have the same loans as the pointer operand.
+ if (BO->isCompoundAssignmentOp())
+ return;
if (BO->isAssignmentOp())
handleAssignment(BO->getLHS(), BO->getRHS());
+ // TODO: Handle assignments involving dereference like `*p = q`.
}
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());
@@ -186,13 +295,12 @@ 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) {
+ if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2 &&
+ hasOrigins(OCE->getArg(0)->getType())) {
handleAssignment(OCE->getArg(0), OCE->getArg(1));
return;
}
- handleFunctionCall(OCE, OCE->getDirectCallee(),
- {OCE->getArgs(), OCE->getNumArgs()},
- /*IsGslConstruction=*/false);
+ VisitCallExpr(OCE);
}
void FactsGenerator::VisitCXXFunctionalCastExpr(
@@ -206,7 +314,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.
@@ -216,11 +324,21 @@ void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
void FactsGenerator::VisitMaterializeTemporaryExpr(
const MaterializeTemporaryExpr *MTE) {
- if (!hasOrigin(MTE))
+ OriginList *MTEList = getOriginsList(*MTE);
+ if (!MTEList)
return;
- // A temporary object's origin is the same as the origin of the
- // expression that initializes it.
- killAndFlowOrigin(*MTE, *MTE->getSubExpr());
+ OriginList *SubExprList = getOriginsList(*MTE->getSubExpr());
+ if (MTE->isGLValue()) {
+ assert(!SubExprList ||
+ MTEList->getLength() == SubExprList->getLength() + 1 &&
+ "MTE top level origin should contain a loan to the MTE itself");
+ MTEList = getRValueOrigins(MTE, MTEList);
+ // TODO: Issue a loan to the MTE.
+ flow(MTEList, SubExprList, /*Kill=*/true);
+ } else {
+ assert(MTE->isXValue());
+ flow(MTEList, SubExprList, /*Kill=*/true);
+ }
}
void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
@@ -244,13 +362,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
+
+ const Expr *Arg = CCE->getArg(0);
+ if (isGslPointerType(Arg->getType())) {
+ OriginList *ArgList = getOriginsList(*Arg);
+ assert(ArgList && "GSL pointer argument should have an origin list");
+ // GSL pointer is constructed from another gsl pointer.
+ // Example:
+ // View(View v);
+ // View(const View &v);
+ ArgList = getRValueOrigins(Arg, ArgList);
+ flow(getOriginsList(*CCE), ArgList, /*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
@@ -261,8 +390,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
const FunctionDecl *FD,
ArrayRef<const Expr *> Args,
bool IsGslConstruction) {
+ OriginList *CallList = getOriginsList(*Call);
// Ignore functions returning values with no origin.
- if (!FD || !hasOrigin(Call))
+ if (!FD || !CallList)
return;
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
const ParmVarDecl *PVD = nullptr;
@@ -275,22 +405,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) {
+ OriginList *ArgList = getOriginsList(*Args[I]);
+ if (!ArgList)
+ continue;
+ if (IsGslConstruction) {
+ // TODO: document with code example.
+ // std::string_view(const std::string_view& from)
+ if (isGslPointerType(Args[I]->getType())) {
+ assert(!Args[I]->isGLValue() || ArgList->getLength() >= 2);
+ ArgList = getRValueOrigins(Args[I], ArgList);
+ }
+ if (isGslOwnerType(Args[I]->getType())) {
+ // GSL construction creates a view that borrows from arguments.
+ // This implies flowing origins through the list structure.
+ flow(CallList, ArgList, 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>(
+ CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc));
+ KillSrc = false;
}
+ }
}
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
@@ -314,39 +463,28 @@ 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;
- }
+ OriginList *List = getOriginsList(*DRE);
+ if (!List)
+ return;
+ // Remove the outer layer of origin which borrows from the decl directly. This
+ // is a use of the underlying decl.
+ List = getRValueOrigins(DRE, List);
+ // Skip if there is no inner origin (e.g., when it is not a pointer type).
+ if (!List)
+ return;
+ UseFact *UF = FactMgr.createFact<UseFact>(DRE, List);
+ 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();
}
// Creates an IssueFact for a new placeholder loan for each pointer or reference
@@ -358,13 +496,13 @@ llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
llvm::SmallVector<Fact *> PlaceholderLoanFacts;
for (const ParmVarDecl *PVD : FD->parameters()) {
- if (hasOrigin(PVD)) {
- const PlaceholderLoan *L =
- FactMgr.getLoanMgr().createLoan<PlaceholderLoan>(PVD);
- OriginID OID = FactMgr.getOriginMgr().getOrCreate(*PVD);
- PlaceholderLoanFacts.push_back(
- FactMgr.createFact<IssueFact>(L->getID(), OID));
- }
+ OriginList *List = getOriginsList(*PVD);
+ if (!List)
+ continue;
+ const PlaceholderLoan *L =
+ FactMgr.getLoanMgr().createLoan<PlaceholderLoan>(PVD);
+ PlaceholderLoanFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
}
return PlaceholderLoanFacts;
}
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..f567cdc548ba7 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -121,13 +121,21 @@ 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 (const OriginList *Cur = UF.getUsedOrigins(); Cur;
+ Cur = Cur->peelOuterOrigin()) {
+ OriginID OID = Cur->getOuterOriginID();
+ // 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..01a74d30408ea 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -59,7 +59,9 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
break;
}
case Fact::Kind::Use:
- CheckOrigin(F->getAs<UseFact>()->getUsedOrigin());
+ for (const OriginList *Cur = F->getAs<UseFact>()->getUsedOrigins(); Cur;
+ Cur = Cur->peelOuterOrigin())
+ CheckOrigin(Cur->getOuterOriginID());
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..b2f1af3d8d7c3 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -7,70 +7,149 @@
//===----------------------------------------------------------------------===//
#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);
}
-Origin &OriginManager::addOrigin(OriginID ID, const clang::ValueDecl &D) {
- AllOrigins.emplace_back(ID, &D);
- return AllOrigins.back();
+/// 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::Expr &E) {
- AllOrigins.emplace_back(ID, &E);
- 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();
}
-// 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())
- 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);
+OriginList *OriginManager::createNode(const ValueDecl *D, QualType QT) {
+ OriginID NewID = getNextOriginID();
+ AllOrigins.emplace_back(NewID, D, QT.getTypePtrOrNull());
+ return new (ListAllocator.Allocate<OriginList>()) OriginList(NewID);
}
-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);
+OriginList *OriginManager::createNode(const Expr *E, QualType QT) {
+ OriginID NewID = getNextOriginID();
+ AllOrigins.emplace_back(NewID, E, QT.getTypePtrOrNull());
+ return new (ListAllocator.Allocate<OriginList>()) OriginList(NewID);
+}
- return It->second;
+template <typename T>
+OriginList *OriginManager::buildListForType(QualType QT, const T *Node) {
+ assert(hasOrigins(QT) && "buildListForType called for non-pointer type");
+ OriginList *Head = 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))
+ Head->setInnerOriginList(buildListForType(PointeeTy, Node));
+ }
+ return Head;
}
-OriginID OriginManager::getOrCreate(const Expr &E) {
- auto It = ExprToOriginID.find(&E);
- if (It != ExprToOriginID.end())
+OriginList *OriginManager::getOrCreateList(const ValueDecl *D) {
+ if (!hasOrigins(D->getType()))
+ return nullptr;
+ auto It = DeclToList.find(D);
+ if (It != DeclToList.end())
return It->second;
+ return DeclToList[D] = buildListForType(D->getType(), D);
+}
- OriginID NewID = getNextOriginID();
- addOrigin(NewID, E);
- ExprToOriginID[&E] = NewID;
- return NewID;
+OriginList *OriginManager::getOrCreateList(const Expr *E) {
+ if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E)
+ return getOrCreateList(ParenIgnored);
+
+ if (!hasOrigins(E))
+ return nullptr;
+
+ auto It = ExprToList.find(E);
+ if (It != ExprToList.end())
+ return It->second;
+
+ QualType Type = E->getType();
+
+ // Special handling for DeclRefExpr to share origins with the underlying decl.
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ OriginList *Head = nullptr;
+ // For non-reference declarations (e.g., `int* p`), the DeclRefExpr is an
+ // lvalue (addressable) that can be borrowed, so we create an outer origin
+ // for the lvalue itself, with the pointee being the declaration's list.
+ // This models taking the address: `&p` borrows the storage of `p`, not what
+ // `p` points to.
+ if (doesDeclHaveStorage(DRE->getDecl())) {
+ Head = createNode(DRE, QualType{});
+ // This ensures origin sharing: multiple DeclRefExprs to the same
+ // declaration share the same underlying origins.
+ Head->setInnerOriginList(getOrCreateList(DRE->getDecl()));
+ } else {
+ // For reference-typed declarations (e.g., `int& r = p`) which have no
+ // storage, the DeclRefExpr directly reuses the declaration's list since
+ // references don't add an extra level of indirection at the expression
+ // level.
+ Head = getOrCreateList(DRE->getDecl());
+ }
+ return ExprToList[E] = Head;
+ }
+
+ // If E is an lvalue , it refers to storage. We model this storage as the
+ // first level of origin list, as if it were a reference, because l-values are
+ // addressable.
+ if (E->isGLValue() && !Type->isReferenceType())
+ Type = AST.getLValueReferenceType(Type);
+ return ExprToList[E] = buildListForType(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";
+ }
+ if (O.Ty)
+ OS << ", Type : " << QualType(O.Ty, 0).getAsString();
+ OS << ")";
}
const Origin &OriginManager::getOrigin(OriginID ID) const {
@@ -78,14 +157,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 321445dd4e1ff..2dbbfc5e90a17 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3027,6 +3027,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-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 7b6fc9201af6d..6d5711deba1cf 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -11,69 +11,38 @@ struct MyObj {
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: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
+// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr, Decl: x))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_DRE_X]] (Expr: DeclRefExpr, Decl: x)
MyObj* p = &x;
-// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_ADDR_X]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Read)
return p;
-// CHECK: Use ([[O_P]] (Decl: p), Read)
-// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
+// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : MyObj *)
// CHECK: Expire ([[L_X]] (Path: x))
-// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
+// CHECK: Expire ({{[0-9]+}} (Path: p))
+// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr, Type : MyObj *))
}
-
-// Pointer Assignment and Return
-// CHECK-LABEL: Function: assign_and_return_local_addr
-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: OriginFlow (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr))
- MyObj* ptr1 = &y;
-// 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: 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: OriginFlow (Dest: [[O_PTR1_RVAL_2:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR1]] (Decl: ptr1))
-// CHECK: Use ({{[0-9]+}} (Decl: ptr2), Write)
-// 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: OriginFlow (Dest: [[O_PTR2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
-// CHECK: Use ([[O_PTR2]] (Decl: ptr2), Write)
-// CHECK: OriginFlow (Dest: [[O_PTR2]] (Decl: ptr2), Src: [[O_PTR2_RVAL]] (Expr: ImplicitCastExpr))
- return ptr2;
-// CHECK: Use ([[O_PTR2]] (Decl: ptr2), Read)
-// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
-// CHECK: Expire ([[L_Y]] (Path: y))
-// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
-}
-
-// Return of Non-Pointer Type
-// CHECK-LABEL: Function: return_int_val
-int return_int_val() {
- int x = 10;
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr))
-// CHECK: Expire ([[L_X:[0-9]+]] (Path: x))
- return x;
-}
-// CHECK-NEXT: End of Block
-
-
// Loan Expiration (Automatic Variable, C++)
// CHECK-LABEL: Function: loan_expires_cpp
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: OriginFlow (Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr))
+// CHECK: Issue ([[L_OBJ:[0-9]+]] (Path: obj), ToOrigin: [[O_DRE_OBJ:[0-9]+]] (Expr: DeclRefExpr, Decl: obj))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr, Decl: obj)
MyObj* pObj = &obj;
-// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pObj), Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pObj, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator, Type : MyObj *)
// CHECK: Expire ([[L_OBJ]] (Path: obj))
}
@@ -82,117 +51,41 @@ void loan_expires_cpp() {
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: OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
+// CHECK: Issue ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj), ToOrigin: [[O_DRE_TRIVIAL:[0-9]+]] (Expr: DeclRefExpr, Decl: trivial_obj))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator, Type : int *)
+// CHECK-NEXT: Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr, Decl: trivial_obj)
int* pTrivialObj = &trivial_obj;
-// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
-// CHECK: Expire ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pTrivialObj, Type : int *)
+// CHECK-NEXT: Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator, Type : int *)
+// CHECK: Expire ([[L_TRIVIAL_OBJ]] (Path: trivial_obj))
// CHECK-NEXT: End of Block
}
-// Trivial Destructors
-// CHECK-LABEL: Function: return_int_pointer
-int* return_int_pointer() {
- int* ptr;
-// CHECK: Block B{{[0-9]+}}:
- int x = 1;
-// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
- ptr = &x;
-// CHECK: Use ([[O_PTR:[0-9]+]] (Decl: ptr), Write)
-// CHECK: OriginFlow (Dest: [[O_PTR]] (Decl: ptr), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
-// CHECK: Use ([[O_PTR]] (Decl: ptr), Read)
-// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR]] (Decl: ptr))
-// CHECK: Expire ([[L_X]] (Path: x))
-// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
- return ptr;
-}
-// CHECK-NEXT: End of Block
-
-// CHECK-LABEL: Function: conditional
-void conditional(bool condition) {
- int a = 5;
- int b = 10;
- int* p = nullptr;
-
- if (condition)
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr))
-// 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: 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: 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))
-}
-
-
-// CHECK-LABEL: Function: pointers_in_a_cycle
-void pointers_in_a_cycle(bool condition) {
- MyObj v1{1};
- MyObj v2{1};
- MyObj v3{1};
-
- MyObj* p1 = &v1;
- MyObj* p2 = &v2;
- 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: 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: 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: 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: 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: OriginFlow (Dest: [[O_P2_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P2]] (Decl: p2))
-// CHECK: Use ({{[0-9]+}} (Decl: p1), Write)
-// 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: OriginFlow (Dest: [[O_P3_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P3]] (Decl: p3))
-// CHECK: Use ({{[0-9]+}} (Decl: p2), Write)
-// CHECK: OriginFlow (Dest: [[O_P2]] (Decl: p2), Src: [[O_P3_RVAL]] (Expr: ImplicitCastExpr))
- p3 = temp;
-// CHECK: Use ([[O_TEMP]] (Decl: temp), Read)
-// CHECK: OriginFlow (Dest: [[O_TEMP_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_TEMP]] (Decl: temp))
-// CHECK: Use ({{[0-9]+}} (Decl: p3), Write)
-// CHECK: OriginFlow (Dest: [[O_P3]] (Decl: p3), Src: [[O_TEMP_RVAL]] (Expr: ImplicitCastExpr))
- }
-}
-
// CHECK-LABEL: Function: overwrite_origin
void overwrite_origin() {
MyObj s1;
MyObj s2;
// 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: 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))
+// CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr, Decl: s1))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_DRE_S1]] (Expr: DeclRefExpr, Decl: s1)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_ADDR_S1]] (Expr: UnaryOperator, Type : MyObj *)
p = &s2;
-// CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (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: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
+// CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr, Decl: s2))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_DRE_S2]] (Expr: DeclRefExpr, Decl: s2)
+// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Write)
+// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_ADDR_S2]] (Expr: UnaryOperator, Type : MyObj *)
// CHECK: Expire ([[L_S2]] (Path: s2))
// CHECK: Expire ([[L_S1]] (Path: s1))
}
@@ -202,211 +95,68 @@ void reassign_to_null() {
MyObj s1;
// 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: 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))
+// CHECK: Issue ([[L_S1:[0-9]+]] (Path: s1), ToOrigin: [[O_DRE_S1:[0-9]+]] (Expr: DeclRefExpr, Decl: s1))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_S1:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_DRE_S1]] (Expr: DeclRefExpr, Decl: s1)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_ADDR_S1]] (Expr: UnaryOperator, Type : MyObj *)
p = nullptr;
-// CHECK: OriginFlow (Dest: [[O_NULLPTR_CAST:[0-9]+]] (Expr: ImplicitCastExpr), Src: {{[0-9]+}} (Expr: CXXNullPtrLiteralExpr))
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_NULLPTR_CAST]] (Expr: ImplicitCastExpr))
+// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Write)
+// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *)
+// CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : MyObj *)
// CHECK: Expire ([[L_S1]] (Path: s1))
}
// FIXME: Have a better representation for nullptr than just an empty origin.
// It should be a separate loan and origin kind.
-
-// CHECK-LABEL: Function: reassign_in_if
-void reassign_in_if(bool condition) {
- MyObj s1;
- MyObj s2;
- 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: 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: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
- }
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Expire ([[L_S2]] (Path: s2))
-// CHECK: Expire ([[L_S1]] (Path: s1))
-}
-
-
-// CHECK-LABEL: Function: assign_in_switch
-void assign_in_switch(int mode) {
- MyObj s1;
- MyObj s2;
- MyObj s3;
- MyObj* p = nullptr;
-// CHECK: Block B{{[0-9]+}}:
-// 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: 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: 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: 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: 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: OriginFlow (Dest: [[O_ADDR_S3:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S3]] (Expr: DeclRefExpr))
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S3]] (Expr: UnaryOperator))
- break;
- }
-// CHECK: Block B{{[0-9]+}}:
-// CHECK-DAG: Expire ([[L_S3]] (Path: s3))
-// CHECK-DAG: Expire ([[L_S2]] (Path: s2))
-// CHECK-DAG: Expire ([[L_S1]] (Path: s1))
-}
-
-// CHECK-LABEL: Function: loan_in_loop
-void loan_in_loop(bool condition) {
- MyObj* p = nullptr;
-// CHECK: Block B{{[0-9]+}}:
-// 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: OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator))
-// CHECK: Expire ([[L_INNER]] (Path: inner))
- }
-}
-
-// CHECK-LABEL: Function: loop_with_break
-void loop_with_break(int count) {
- MyObj s1;
- MyObj s2;
- 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: 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: OriginFlow (Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_S2]] (Expr: DeclRefExpr))
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_S2]] (Expr: UnaryOperator))
- break;
- }
- }
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Expire ([[L_S2]] (Path: s2))
-// CHECK: Expire ([[L_S1]] (Path: s1))
-}
-
-// CHECK-LABEL: Function: nested_scopes
-void nested_scopes() {
- MyObj* p = nullptr;
-// CHECK: Block B{{[0-9]+}}:
-// 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: OriginFlow (Dest: [[O_ADDR_OUTER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_OUTER]] (Expr: DeclRefExpr))
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// 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: OriginFlow (Dest: [[O_ADDR_INNER:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_INNER]] (Expr: DeclRefExpr))
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_INNER]] (Expr: UnaryOperator))
- }
-// CHECK: Expire ([[L_INNER]] (Path: inner))
- }
-// CHECK: Expire ([[L_OUTER]] (Path: outer))
-}
-
// CHECK-LABEL: Function: pointer_indirection
void pointer_indirection() {
int a;
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: 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))
+// CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr, Decl: a))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator, Type : int *)
+// CHECK-NEXT: Src: [[O_DRE_A]] (Expr: DeclRefExpr, Decl: a)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : int *)
+// CHECK-NEXT: Src: [[O_ADDR_A]] (Expr: UnaryOperator, Type : int *)
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.
+// CHECK: Use ([[O_P]] (Decl: p, Type : int *), Read)
+// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int **)
+// CHECK-NEXT: Src: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *)
+// CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : int *)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_PP_OUTER:[0-9]+]] (Decl: pp, Type : int **)
+// CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : int **)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_PP_INNER:[0-9]+]] (Decl: pp, Type : int *)
+// CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : int *)
+
+ // FIXME: Propagate origins across dereference unary operator*
int *q = *pp;
-// CHECK: Use ([[O_PP:[0-9]+]] (Decl: pp), Read)
-// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: ImplicitCastExpr))
-}
-
-// CHECK-LABEL: Function: ternary_operator
-// FIXME: Propagate origins across ConditionalOperator.
-void ternary_operator() {
- int a, b;
- int *p;
- p = (a > b) ? &a : &b;
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr))
-
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Issue ([[L_B:[0-9]+]] (Path: b), ToOrigin: [[O_DRE_B:[0-9]+]] (Expr: DeclRefExpr))
-
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Use ({{[0-9]+}} (Decl: p), Write)
-// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: p), Src: {{[0-9]+}} (Expr: ConditionalOperator))
-}
-
-// CHECK-LABEL: Function: test_use_facts
-void usePointer(MyObj*);
-void test_use_facts() {
- MyObj x;
- MyObj *p;
-// CHECK: Block B{{[0-9]+}}:
- p = &x;
-// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (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: OriginFlow (Dest: [[O_P]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
- (void)*p;
-// CHECK: Use ([[O_P]] (Decl: p), Read)
- usePointer(p);
-// CHECK: Use ([[O_P]] (Decl: p), Read)
- p->id = 1;
-// CHECK: Use ([[O_P]] (Decl: p), Read)
- MyObj* q;
- q = p;
-// CHECK: Use ([[O_P]] (Decl: p), Read)
-// CHECK: Use ([[O_Q:[0-9]+]] (Decl: q), Write)
- usePointer(q);
-// CHECK: Use ([[O_Q]] (Decl: q), Read)
- q->id = 2;
-// CHECK: Use ([[O_Q]] (Decl: q), Read)
-// CHECK: Expire ([[L_X]] (Path: x))
+// CHECK: Use ([[O_PP_OUTER]] (Decl: pp, Type : int **), [[O_PP_INNER]] (Decl: pp, Type : int *), Read)
+// CHECK: Issue ({{[0-9]+}} (Path: pp), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: pp))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int **)
+// CHECK-NEXT: Src: [[O_PP_OUTER]] (Decl: pp, Type : int **)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *)
+// CHECK-NEXT: Src: [[O_PP_INNER]] (Decl: pp, Type : int *)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *)
+// CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : int *)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: q, Type : int *)
+// CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *)
}
// CHECK-LABEL: Function: test_use_lifetimebound_call
@@ -414,38 +164,70 @@ MyObj* LifetimeBoundCall(MyObj* x [[clang::lifetimebound]], MyObj* y [[clang::li
void test_use_lifetimebound_call() {
MyObj x, y;
MyObj *p = &x;
-// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
-// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
+// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr, Decl: x))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_DRE_X]] (Expr: DeclRefExpr, Decl: x)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_ADDR_X]] (Expr: UnaryOperator, Type : MyObj *)
MyObj *q = &y;
-// CHECK: Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: OriginFlow (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr))
-// CHECK: OriginFlow (Dest: [[O_Q:[0-9]+]] (Decl: q), Src: [[O_ADDR_Y]] (Expr: UnaryOperator))
+// CHECK: Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] (Expr: DeclRefExpr, Decl: y))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_DRE_Y]] (Expr: DeclRefExpr, Decl: y)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_Q:[0-9]+]] (Decl: q, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_ADDR_Y]] (Expr: UnaryOperator, Type : MyObj *)
MyObj* r = LifetimeBoundCall(p, q);
-// CHECK: Use ([[O_P]] (Decl: p), Read)
-// CHECK: OriginFlow (Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
-// CHECK: Use ([[O_Q]] (Decl: q), Read)
-// CHECK: OriginFlow (Dest: [[O_Q_RVAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_Q]] (Decl: q))
-// CHECK: OriginFlow (Dest: [[O_CALL_EXPR:[0-9]+]] (Expr: CallExpr), Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr))
-// CHECK: OriginFlow (Dest: [[O_CALL_EXPR]] (Expr: CallExpr), Src: [[O_Q_RVAL]] (Expr: ImplicitCastExpr), Merge)
-// CHECK: OriginFlow (Dest: [[O_R:[0-9]+]] (Decl: r), Src: [[O_CALL_EXPR]] (Expr: CallExpr))
+// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Read)
+// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : MyObj *)
+// CHECK: Use ([[O_Q]] (Decl: q, Type : MyObj *), Read)
+// CHECK: Issue ({{[0-9]+}} (Path: q), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: q))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_Q_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_Q]] (Decl: q, Type : MyObj *)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_CALL_EXPR:[0-9]+]] (Expr: CallExpr, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr, Type : MyObj *)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_CALL_EXPR]] (Expr: CallExpr, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_Q_RVAL]] (Expr: ImplicitCastExpr, Type : MyObj *), Merge
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: r, Type : MyObj *)
+// CHECK-NEXT: Src: [[O_CALL_EXPR]] (Expr: CallExpr, Type : MyObj *)
// CHECK: Expire ([[L_Y]] (Path: y))
// CHECK: Expire ([[L_X]] (Path: x))
}
-// CHECK-LABEL: Function: test_conditional_operator
-void test_conditional_operator(bool cond) {
- MyObj x, y;
- MyObj *p = cond ? &x : &y;
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] (Expr: DeclRefExpr))
-// CHECK: OriginFlow (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr))
-// CHECK: Block B{{[0-9]+}}:
-// CHECK: OriginFlow (Dest: [[O_COND_OP:[0-9]+]] (Expr: ConditionalOperator), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
-// CHECK: OriginFlow (Dest: [[O_COND_OP]] (Expr: ConditionalOperator), Src: [[O_ADDR_Y]] (Expr: UnaryOperator), Merge)
-// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_COND_OP]] (Expr: ConditionalOperator))
-// CHECK: Expire ([[L_Y]] (Path: y))
+
+// CHECK-LABEL: Function: test_reference_variable
+void test_reference_variable() {
+ MyObj x;
+ const MyObj* p;
+ const MyObj& y = x;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr, Decl: x))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_CAST_Y:[0-9]+]] (Expr: ImplicitCastExpr, Type : const MyObj &)
+// CHECK-NEXT: Src: [[O_DRE_X]] (Expr: DeclRefExpr, Decl: x)
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_Y:[0-9]+]] (Decl: y, Type : const MyObj &)
+// CHECK-NEXT: Src: [[O_CAST_Y]] (Expr: ImplicitCastExpr, Type : const MyObj &)
+ const MyObj& z = y;
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: [[O_Z:[0-9]+]] (Decl: z, Type : const MyObj &)
+// CHECK-NEXT: Src: [[O_Y]] (Decl: y, Type : const MyObj &)
+ p = &z;
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *)
+// CHECK-NEXT: Src: [[O_Z]] (Decl: z, Type : const MyObj &)
+// CHECK: Use ({{[0-9]+}} (Decl: p, Type : const MyObj *), Write)
+// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
+// CHECK: OriginFlow:
+// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: p, Type : const MyObj *)
+// CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *)
// CHECK: Expire ([[L_X]] (Path: x))
}
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 9f3ccb7fca770..fd79c497ea5d5 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -1,9 +1,13 @@
-// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -fexperimental-lifetime-safety-inference -Wexperimental-lifetime-safety-suggestions -Wexperimental-lifetime-safety -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -fexperimental-lifetime-safety-inference -Wexperimental-lifetime-safety-suggestions -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 {
@@ -16,7 +20,7 @@ View return_view_directly (View a) { // expected-warning {{param should be ma
return a; // expected-note {{param returned here}}
}
-View conditional_return_view (
+View conditional_return_view(
View a, // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
View b, // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
bool c) {
@@ -28,17 +32,46 @@ View conditional_return_view (
return res; // expected-note 2 {{param returned here}}
}
-// FIXME: Fails to generate lifetime suggestion for reference types as these are not handled currently.
-MyObj& return_reference (MyObj& a, MyObj& b, bool c) {
+MyObj& return_reference(MyObj& a, // expected-warning {{param should be marked [[clang::lifetimebound]]}}
+ MyObj& b, // expected-warning {{param should be marked [[clang::lifetimebound]]}}
+ bool c) {
if(c) {
- return a;
+ return a; // expected-note {{param returned here}}
}
- return b;
+ return b; // expected-note {{param returned here}}
+}
+
+const MyObj& return_reference_const(const MyObj& a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}
+ return a; // expected-note {{param returned here}}
+}
+
+MyObj* return_ptr_to_ref(MyObj& a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}
+ return &a; // expected-note {{param returned here}}
+}
+
+// FIXME: Dereference does not propagate loans.
+MyObj& return_ref_to_ptr(MyObj* a) {
+ return *a;
+}
+
+View return_view_from_reference(MyObj& p) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}
+ return p; // expected-note {{param returned here}}
+}
+
+struct Container {
+ MyObj data;
+ const MyObj& getData() [[clang::lifetimebound]] { return data; }
+};
+// FIXME: c.data does not forward loans
+View return_struct_field(const Container& c) {
+ return c.data;
+}
+View return_struct_lifetimebound_getter(Container& c) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}
+ return c.getData().getView(); // expected-note {{param returned here}}
}
-// FIXME: Fails to generate lifetime suggestion for reference types as these are not handled currently.
-View return_view_from_reference (MyObj& p) {
- return p;
+View return_view_from_reference_lifetimebound_member(MyObj& p) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}
+ return p.getView(); // expected-note {{param returned here}}
}
int* return_pointer_directly (int* a) { // expected-warning {{param should be marked [[clang::lifetimebound]]}}.
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 1191469e23df1..eb48d9e674d9a 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,236 @@ void parentheses(bool cond) {
} // expected-note 4 {{destroyed here}}
(void)*p; // expected-note 4 {{later used here}}
}
+
+namespace GH162834 {
+// https://github.com/llvm/llvm-project/issues/162834
+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 fee4e79e27d03..f05a249c373c4 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);
+ OriginList *List =
+ const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr())
+ .getOrCreateList(VD);
+ if (!List) {
+ ADD_FAILURE() << "No origin list found for Var '" << VarName << "'";
+ return std::nullopt;
+ }
+ return List->getOuterOriginID();
}
std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
@@ -827,7 +833,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) {
@@ -880,7 +886,7 @@ TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) {
StringView() = default;
};
- struct String {
+ struct [[gsl::Owner]] String {
~String() {}
operator StringView() const;
};
@@ -916,24 +922,37 @@ 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) {
@@ -1020,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]]);
@@ -1031,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) {
@@ -1068,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]]);
@@ -1084,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