[clang] [LifetimeSafety] Implement multi-level origins (PR #168344)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Wed Nov 19 03:24:07 PST 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/168344
>From 5b9efab397d193c64374483d0bc7939c8a7a8326 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 17 Nov 2025 10:53:05 +0000
Subject: [PATCH] lifetime-safety-multi-origin
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 9 +-
.../Analyses/LifetimeSafety/FactsGenerator.h | 19 +-
.../LifetimeSafety/LifetimeAnnotations.h | 5 +
.../Analyses/LifetimeSafety/Origins.h | 96 +++++-
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 5 +-
.../LifetimeSafety/FactsGenerator.cpp | 316 +++++++++++++-----
.../LifetimeSafety/LifetimeAnnotations.cpp | 31 ++
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 20 +-
.../LifetimeSafety/LoanPropagation.cpp | 3 +-
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 128 ++++---
clang/lib/Sema/CheckExprLifetime.cpp | 69 ++--
clang/lib/Sema/CheckExprLifetime.h | 3 -
clang/lib/Sema/SemaAttr.cpp | 4 +-
clang/test/Sema/warn-lifetime-safety.cpp | 127 +++++--
.../unittests/Analysis/LifetimeSafetyTest.cpp | 5 +-
15 files changed, 582 insertions(+), 258 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index b9cad5340c940..3ae2458ebd239 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -152,7 +152,8 @@ class ReturnOfOriginFact : public Fact {
class UseFact : public Fact {
const Expr *UseExpr;
- OriginID OID;
+ // The origins of the expression being used.
+ llvm::SmallVector<OriginID, 1> OIDs;
// True if this use is a write operation (e.g., left-hand side of assignment).
// Write operations are exempted from use-after-free checks.
bool IsWritten = false;
@@ -160,10 +161,10 @@ class UseFact : public Fact {
public:
static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
- UseFact(const Expr *UseExpr, OriginManager &OM)
- : Fact(Kind::Use), UseExpr(UseExpr), OID(OM.get(*UseExpr)) {}
+ UseFact(const Expr *UseExpr, llvm::ArrayRef<OriginID> OIDs)
+ : Fact(Kind::Use), UseExpr(UseExpr), OIDs(OIDs.begin(), OIDs.end()) {}
- OriginID getUsedOrigin() const { return OID; }
+ llvm::ArrayRef<OriginID> getUsedOrigins() const { return OIDs; }
const Expr *getUseExpr() const { return UseExpr; }
void markAsWritten() { IsWritten = true; }
bool isWritten() const { return IsWritten; }
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 4c8ab3f859a49..bf19f87f9e0b7 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -50,6 +50,11 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
private:
+ OriginTree *getTree(const ValueDecl &D);
+ OriginTree *getTree(const Expr &E);
+
+ void flow(OriginTree *Dst, OriginTree *Src, bool Kill);
+
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt);
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
@@ -64,32 +69,24 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
template <typename Destination, typename Source>
void flowOrigin(const Destination &D, const Source &S) {
- OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
- OriginID SrcOID = FactMgr.getOriginMgr().get(S);
- CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
- DestOID, SrcOID, /*KillDest=*/false));
+ flow(getTree(D), getTree(S), /*Kill=*/false);
}
template <typename Destination, typename Source>
void killAndFlowOrigin(const Destination &D, const Source &S) {
- OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
- OriginID SrcOID = FactMgr.getOriginMgr().get(S);
- CurrentBlockFacts.push_back(
- FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID, /*KillDest=*/true));
+ flow(getTree(D), getTree(S), /*Kill=*/true);
}
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
/// If so, creates a `TestPointFact` and returns true.
bool handleTestPoint(const CXXFunctionalCastExpr *FCE);
- void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);
-
// A DeclRefExpr will be treated as a use of the referenced decl. It will be
// checked for use-after-free unless it is later marked as being written to
// (e.g. on the left-hand side of an assignment).
void handleUse(const DeclRefExpr *DRE);
- void markUseAsWrite(const DeclRefExpr *DRE);
+ void markUseAsWrite(const Expr *E);
FactManager &FactMgr;
AnalysisDeclContext &AC;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index f02969e0a9563..1a16fb82f9a84 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -38,6 +38,11 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
/// method or because it's a normal assignment operator.
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
+// Tells whether the type is annotated with [[gsl::Pointer]].
+bool isGslPointerType(QualType QT);
+// Tells whether the type is annotated with [[gsl::Owner]].
+bool isGslOwnerType(QualType QT);
+
} // namespace clang::lifetimes
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 56b9010f41fa2..171719785e77e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -52,28 +52,77 @@ struct Origin {
}
};
+/// A tree of origins representing levels of indirection for pointer-like types.
+///
+/// Each node in the tree contains an OriginID representing a level of
+/// indirection. The tree structure captures the multi-level nature of
+/// pointer and reference types in the lifetime analysis.
+///
+/// Examples:
+/// - For `int& x`, the tree has depth 2:
+/// * Root: origin for the reference storage itself (the lvalue `x`)
+/// * Pointee: origin for what `x` refers to
+///
+/// - For `int* p`, the tree has depth 2:
+/// * Root: origin for the pointer variable `p`
+/// * Pointee: origin for what `p` points to
+///
+/// - For `View v` (where View is gsl::Pointer), the tree has depth 2:
+/// * Root: origin for the view object itself
+/// * Pointee: origin for what the view refers to
+///
+/// - For `int** pp`, the tree has depth 3:
+/// * Root: origin for `pp` itself
+/// * Pointee: origin for `*pp` (what `pp` points to)
+/// * Pointee->Pointee: origin for `**pp` (what `*pp` points to)
+///
+/// The tree structure enables the analysis to track how loans flow through
+/// different levels of indirection when assignments and dereferences occur.
+struct OriginTree {
+ OriginID OID;
+ OriginTree *Pointee = nullptr;
+
+ OriginTree(OriginID OID) : OID(OID) {}
+
+ size_t getDepth() const {
+ size_t Depth = 1;
+ const OriginTree *T = this;
+ while (T->Pointee) {
+ T = T->Pointee;
+ Depth++;
+ }
+ return Depth;
+ }
+};
+
+bool isPointerLikeType(QualType QT);
+
/// Manages the creation, storage, and retrieval of origins for pointer-like
/// variables and expressions.
class OriginManager {
public:
- OriginManager() = default;
-
- Origin &addOrigin(OriginID ID, const clang::ValueDecl &D);
- Origin &addOrigin(OriginID ID, const clang::Expr &E);
-
- // TODO: Mark this method as const once we remove the call to getOrCreate.
- OriginID get(const Expr &E);
-
- OriginID get(const ValueDecl &D);
-
- OriginID getOrCreate(const Expr &E);
+ /// Gets or creates the OriginTree for a given ValueDecl.
+ ///
+ /// Creates a tree structure mirroring the levels of indirection in the
+ /// declaration's type (e.g., `int** p` creates depth 3).
+ ///
+ /// \returns The OriginTree, or nullptr if the type is not pointer-like.
+ OriginTree *getOrCreateTree(const ValueDecl *D);
+
+ /// Gets or creates the OriginTree for a given Expr.
+ ///
+ /// Creates a tree based on the expression's type and value category:
+ /// - Lvalues get an implicit reference level (modeling addressability)
+ /// - Rvalues of non-pointer type return nullptr (no trackable origin)
+ /// - DeclRefExpr may reuse the underlying declaration's tree
+ ///
+ /// \returns The OriginTree, or nullptr for non-pointer rvalues.
+ OriginTree *getOrCreateTree(const Expr *E, ASTContext &Ctx);
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 +130,27 @@ class OriginManager {
private:
OriginID getNextOriginID() { return NextOriginID++; }
+ OriginTree *createNode(const ValueDecl *D) {
+ OriginID NewID = getNextOriginID();
+ AllOrigins.emplace_back(NewID, D);
+ return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
+ }
+ OriginTree *createNode(const Expr* E) {
+ OriginID NewID = getNextOriginID();
+ AllOrigins.emplace_back(NewID, E);
+ return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
+ }
+
+ template <typename T>
+ OriginTree *buildTreeForType(QualType QT, const T *Node);
+
OriginID NextOriginID{0};
- /// TODO(opt): Profile and evaluate the usefullness of small buffer
+ /// TODO(opt): Profile and evaluate the usefulness of small buffer
/// optimisation.
llvm::SmallVector<Origin> AllOrigins;
- llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
- llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
+ llvm::BumpPtrAllocator TreeAllocator;
+ llvm::DenseMap<const clang::ValueDecl *, OriginTree *> DeclToTree;
+ llvm::DenseMap<const clang::Expr *, OriginTree *> ExprToTree;
};
} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 190c038f46401..4c97577dac6db 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -53,7 +53,10 @@ void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const {
OS << "Use (";
- OM.dump(getUsedOrigin(), OS);
+ for (OriginID OID : getUsedOrigins()) {
+ OM.dump(OID, OS);
+ OS << " ";
+ }
OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
}
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 381ff99aae420..a6a1a3498d370 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -6,6 +6,10 @@
//
//===----------------------------------------------------------------------===//
+#include <cassert>
+#include <string>
+
+#include "clang/AST/OperationKinds.h"
#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
@@ -15,28 +19,34 @@
namespace clang::lifetimes::internal {
using llvm::isa_and_present;
-static bool isGslPointerType(QualType QT) {
- if (const auto *RD = QT->getAsCXXRecordDecl()) {
- // We need to check the template definition for specializations.
- if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
- return CTSD->getSpecializedTemplate()
- ->getTemplatedDecl()
- ->hasAttr<PointerAttr>();
- return RD->hasAttr<PointerAttr>();
- }
- return false;
+// Check if a type has an origin.
+static bool hasOrigin(const Expr *E) {
+ return E->isGLValue() || isPointerLikeType(E->getType());
}
-static bool isPointerType(QualType QT) {
- return QT->isPointerOrReferenceType() || isGslPointerType(QT);
+OriginTree *FactsGenerator::getTree(const ValueDecl &D) {
+ return FactMgr.getOriginMgr().getOrCreateTree(&D);
}
-// Check if a type has an origin.
-static bool hasOrigin(const Expr *E) {
- return E->isGLValue() || isPointerType(E->getType());
+OriginTree *FactsGenerator::getTree(const Expr &E) {
+ return FactMgr.getOriginMgr().getOrCreateTree(&E, AC.getASTContext());
}
-static bool hasOrigin(const VarDecl *VD) {
- return isPointerType(VD->getType());
+void FactsGenerator::flow(OriginTree *Dst, OriginTree *Src, bool Kill) {
+ if (!Dst)
+ return;
+ DEBUG_WITH_TYPE("multi", llvm::errs()
+ << "Dst depth = " << Dst->getDepth() << "\n");
+ assert(Src);
+ DEBUG_WITH_TYPE("multi", llvm::errs()
+ << "Src depth = " << Src->getDepth() << "\n");
+ assert(Dst->getDepth() == Src->getDepth() && "Trees must be same shape");
+
+ while (Dst && Src) {
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<OriginFlowFact>(Dst->OID, Src->OID, Kill));
+ Dst = Dst->Pointee;
+ Src = Src->Pointee;
+ }
}
/// Creates a loan for the storage path of a given declaration reference.
@@ -53,6 +63,19 @@ static const Loan *createLoan(FactManager &FactMgr, const DeclRefExpr *DRE) {
}
void FactsGenerator::run() {
+
+ if (const Decl *D = AC.getDecl()) {
+ if (const auto *ND = dyn_cast<NamedDecl>(D)) {
+ DEBUG_WITH_TYPE("multi",
+ llvm::errs()
+ << "=============================================\n");
+ DEBUG_WITH_TYPE("multi", llvm::dbgs()
+ << "Function: "
+ << ND->getQualifiedNameAsString() << "\n");
+ // DEBUG_WITH_TYPE("multi", AC.getBody()->dumpColor());
+ }
+ }
+
llvm::TimeTraceScope TimeProfile("FactGenerator");
// Iterate through the CFG blocks in reverse post-order to ensure that
// initializations and destructions are processed in the correct sequence.
@@ -60,10 +83,12 @@ void FactsGenerator::run() {
CurrentBlockFacts.clear();
for (unsigned I = 0; I < Block->size(); ++I) {
const CFGElement &Element = Block->Elements[I];
- if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
+ if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) {
+ DEBUG_WITH_TYPE("multi", llvm::errs() << "Processing: \n");
+ DEBUG_WITH_TYPE("multi", CS->getStmt()->dumpColor());
Visit(CS->getStmt());
- else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
- Element.getAs<CFGAutomaticObjDtor>())
+ } else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
+ Element.getAs<CFGAutomaticObjDtor>())
handleDestructor(*DtorOpt);
}
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
@@ -73,12 +98,33 @@ void FactsGenerator::run() {
void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) {
for (const Decl *D : DS->decls())
if (const auto *VD = dyn_cast<VarDecl>(D))
- if (hasOrigin(VD))
- if (const Expr *InitExpr = VD->getInit())
- killAndFlowOrigin(*VD, *InitExpr);
+ if (const Expr *InitExpr = VD->getInit()) {
+ OriginTree *VDTree = getTree(*VD); // Dst
+ if (!VDTree)
+ continue;
+ OriginTree *InitTree = getTree(*InitExpr); // Src
+ if (!InitTree)
+ continue;
+ // TODO: Document escape hatch.
+ // e.g. Ranges&& constructed from move.
+ if (VD->getType()->isRValueReferenceType() && InitExpr->isXValue()) {
+ // If the variable is an rvalue reference or
+ // the initializer is an
+ flow(VDTree->Pointee, InitTree->Pointee, /*Kill=*/true);
+ continue;
+ }
+ // DS->dumpColor();
+ // llvm::errs() << "VD: " << VD->getName() << "\n";
+ // llvm::errs() << "isGslPointerType(VD->getType())"
+ // << isGslPointerType(VD->getType()) << "\n";
+ // llvm::errs() << "VDTree depth = " << VDTree->getDepth() << "\n";
+ flow(VDTree, InitTree, /*Kill=*/true);
+ }
}
void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
+ 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.
@@ -90,12 +136,14 @@ void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
// single-origin model cannot distinguish between a loan to the variable's
// storage and a loan to what it points to. A multi-origin model would be
// required for this.
- if (!isPointerType(DRE->getType())) {
- if (const Loan *L = createLoan(FactMgr, DRE)) {
- OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE);
- CurrentBlockFacts.push_back(
- FactMgr.createFact<IssueFact>(L->ID, ExprOID));
- }
+ if (!isPointerLikeType(DRE->getType()) &&
+ !DRE->getDecl()->getType()->isReferenceType()) {
+ const Loan *L = createLoan(FactMgr, DRE);
+ assert(L);
+ OriginTree *tree = getTree(*DRE);
+ assert(tree && "lvalue DRE of non-pointer type should have an origin tree");
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->ID, tree->OID));
}
}
@@ -109,12 +157,14 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
// Specifically for conversion operators,
// like `std::string_view p = std::string{};`
- if (isGslPointerType(MCE->getType()) &&
- isa_and_present<CXXConversionDecl>(MCE->getCalleeDecl())) {
+ if (isa_and_present<CXXConversionDecl>(MCE->getCalleeDecl()) &&
+ isGslPointerType(MCE->getType()) &&
+ isGslOwnerType(MCE->getImplicitObjectArgument()->getType())) {
// The argument is the implicit object itself.
handleFunctionCall(MCE, MCE->getMethodDecl(),
{MCE->getImplicitObjectArgument()},
/*IsGslConstruction=*/true);
+ return;
}
if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
// Construct the argument list, with the implicit 'this' object as the
@@ -136,15 +186,41 @@ void FactsGenerator::VisitCXXNullPtrLiteralExpr(
const CXXNullPtrLiteralExpr *N) {
/// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
/// pointers can use the same type of loan.
- FactMgr.getOriginMgr().getOrCreate(*N);
+ getTree(*N);
}
void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
- if (!hasOrigin(ICE))
+ OriginTree *Dest = getTree(*ICE);
+ if (!Dest)
return;
- // 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());
+ OriginTree *SrcTree = getTree(*ICE->getSubExpr());
+
+ if (ICE->getCastKind() == CK_LValueToRValue) {
+ // TODO: Decide what to do for x-values here.
+ if (!ICE->getSubExpr()->isLValue())
+ return;
+
+ assert(SrcTree && "LValue being cast to RValue has no origin tree");
+ // The result of an LValue-to-RValue cast on a reference-to-pointer like
+ // has the inner origin. Get rid of the outer origin.
+ flow(getTree(*ICE), SrcTree->Pointee, /*Kill=*/true);
+ return;
+ }
+ if (ICE->getCastKind() == CK_NullToPointer) {
+ getTree(*ICE);
+ // TODO: Flow into them a null origin.
+ return;
+ }
+ if (ICE->getCastKind() == CK_NoOp ||
+ ICE->getCastKind() == CK_ConstructorConversion ||
+ ICE->getCastKind() == CK_UserDefinedConversion)
+ flow(Dest, SrcTree, /*Kill=*/true);
+ if (ICE->getCastKind() == CK_FunctionToPointerDecay ||
+ ICE->getCastKind() == CK_BuiltinFnToFnPtr ||
+ ICE->getCastKind() == CK_ArrayToPointerDecay) {
+ // Ignore function-to-pointer decays.
+ return;
+ }
}
void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) {
@@ -152,7 +228,7 @@ void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) {
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()))
+ if (isPointerLikeType(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
@@ -164,19 +240,34 @@ 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);
- CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID));
- }
+ if (OriginTree *Tree = getTree(*RetExpr))
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<ReturnOfOriginFact>(Tree->OID));
}
}
void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
- if (BO->isAssignmentOp())
- handleAssignment(BO->getLHS(), BO->getRHS());
+ if (BO->isCompoundAssignmentOp())
+ return;
+ if (BO->isAssignmentOp()) {
+ const Expr *LHSExpr = BO->getLHS();
+ const Expr *RHSExpr = BO->getRHS();
+
+ if (const auto *DRE_LHS =
+ dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
+ OriginTree *LHSTree = getTree(*DRE_LHS);
+ OriginTree *RHSTree = getTree(*RHSExpr);
+ // TODO: Handle reference types.
+ markUseAsWrite(DRE_LHS);
+ // Kill the old loans of the destination origin and flow the new loans
+ // from the source origin.
+ flow(LHSTree->Pointee, RHSTree, /*Kill=*/true);
+ }
+ }
}
void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
+
if (hasOrigin(CO)) {
// Merge origins from both branches of the conditional operator.
// We kill to clear the initial state and merge both origins into it.
@@ -188,8 +279,26 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
// Assignment operators have special "kill-then-propagate" semantics
// and are handled separately.
- if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) {
- handleAssignment(OCE->getArg(0), OCE->getArg(1));
+ if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2) {
+
+ const Expr *LHSExpr = OCE->getArg(0);
+ const Expr *RHSExpr = OCE->getArg(1);
+
+ if (const auto *DRE_LHS =
+ dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
+ OriginTree *LHSTree = getTree(*DRE_LHS);
+ OriginTree *RHSTree = getTree(*RHSExpr);
+
+ // TODO: Doc why.
+ // Construction of GSL! View &(const View &).
+ if (RHSExpr->isGLValue())
+ RHSTree = RHSTree->Pointee;
+ // TODO: Handle reference types.
+ markUseAsWrite(DRE_LHS);
+ // Kill the old loans of the destination origin and flow the new loans
+ // from the source origin.
+ flow(LHSTree->Pointee, RHSTree, /*Kill=*/true);
+ }
return;
}
handleFunctionCall(OCE, OCE->getDirectCallee(),
@@ -218,11 +327,24 @@ void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
void FactsGenerator::VisitMaterializeTemporaryExpr(
const MaterializeTemporaryExpr *MTE) {
- if (!hasOrigin(MTE))
+ OriginTree *MTETree = getTree(*MTE);
+ OriginTree *SubExprTree = getTree(*MTE->getSubExpr());
+ if (!MTETree)
return;
- // A temporary object's origin is the same as the origin of the
- // expression that initializes it.
- killAndFlowOrigin(*MTE, *MTE->getSubExpr());
+ if (MTE->isGLValue()) {
+ assert(!SubExprTree ||
+ MTETree->getDepth() == SubExprTree->getDepth() + 1 && "todo doc.");
+ // Issue a loan to the MTE.
+ // const Loan *L = createLoan(FactMgr, MTE);
+ // CurrentBlockFacts.push_back(
+ // FactMgr.createFact<IssueFact>(L->ID, MTETree->OID));
+ if (SubExprTree)
+ flow(MTETree->Pointee, SubExprTree, /*Kill=*/true);
+ } else {
+ assert(MTE->isXValue());
+ flow(MTETree, SubExprTree, /*Kill=*/true);
+ }
+ // TODO: MTE top level origin should contain a loan to the MTE itself.
}
void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
@@ -251,13 +373,23 @@ void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
assert(isGslPointerType(CCE->getType()));
if (CCE->getNumArgs() != 1)
return;
- if (hasOrigin(CCE->getArg(0)))
- killAndFlowOrigin(*CCE, *CCE->getArg(0));
- else
+
+ if (hasOrigin(CCE->getArg(0))) {
+ // TODO: Add code example here.
+ OriginTree *ArgTree = getTree(*CCE->getArg(0));
+ assert(ArgTree && "GSL pointer argument should have an origin tree");
+ // GSL pointer is constructed from another gsl pointer.
+ // TODO: Add proper assertions.
+ if (ArgTree->getDepth() == 2)
+ ArgTree = ArgTree->Pointee;
+ flow(getTree(*CCE), ArgTree, /*Kill=*/true);
+ } else {
// This could be a new borrow.
+ // TODO: Add code example here.
handleFunctionCall(CCE, CCE->getConstructor(),
{CCE->getArgs(), CCE->getNumArgs()},
/*IsGslConstruction=*/true);
+ }
}
/// Checks if a call-like expression creates a borrow by passing a value to a
@@ -268,8 +400,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
const FunctionDecl *FD,
ArrayRef<const Expr *> Args,
bool IsGslConstruction) {
+ OriginTree *CallTree = getTree(*Call);
// Ignore functions returning values with no origin.
- if (!FD || !hasOrigin(Call))
+ if (!FD || !CallTree)
return;
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
const ParmVarDecl *PVD = nullptr;
@@ -282,22 +415,39 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
// For explicit arguments, find the corresponding parameter
// declaration.
PVD = Method->getParamDecl(I - 1);
- } else if (I < FD->getNumParams())
+ } else if (I < FD->getNumParams()) {
// For free functions or static methods.
PVD = FD->getParamDecl(I);
+ }
return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
};
if (Args.empty())
return;
- bool killedSrc = false;
- for (unsigned I = 0; I < Args.size(); ++I)
- if (IsGslConstruction || IsArgLifetimeBound(I)) {
- if (!killedSrc) {
- killedSrc = true;
- killAndFlowOrigin(*Call, *Args[I]);
- } else
- flowOrigin(*Call, *Args[I]);
+ bool KillSrc = true;
+ for (unsigned I = 0; I < Args.size(); ++I) {
+ OriginTree *ArgTree = getTree(*Args[I]);
+ if (!ArgTree)
+ continue;
+ if (IsGslConstruction) {
+ // TODO: document with code example.
+ // std::string_view(const std::string_view& from)
+ if (isGslPointerType(Args[I]->getType()) && Args[I]->isGLValue()) {
+ assert(ArgTree->getDepth() >= 2);
+ ArgTree = ArgTree->Pointee;
+ }
+ // GSL construction creates a view that borrows from arguments.
+ // This implies flowing origins through the tree structure.
+ flow(CallTree, ArgTree, KillSrc);
+ KillSrc = false;
+ } else if (IsArgLifetimeBound(I)) {
+ // Lifetimebound on a non-GSL-ctor function means the returned
+ // pointer/reference itself must not outlive the arguments. This
+ // only constraints the top-level origin.
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ CallTree->OID, ArgTree->OID, KillSrc));
+ KillSrc = false;
}
+ }
}
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
@@ -321,36 +471,32 @@ 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;
+ if (!isPointerLikeType(DRE->getType()))
+ return;
+ OriginTree *tree = getTree(*DRE);
+ if (!tree)
+ return;
+ llvm::SmallVector<OriginID, 1> UsedOrigins;
+ OriginTree *T = tree;
+ while (T) {
+ UsedOrigins.push_back(T->OID);
+ T = T->Pointee;
}
+ UseFact *UF = FactMgr.createFact<UseFact>(DRE, UsedOrigins);
+ CurrentBlockFacts.push_back(UF);
+ assert(!UseFacts.contains(DRE));
+ UseFacts[DRE] = UF;
}
-void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
- if (!isPointerType(DRE->getType()))
+void FactsGenerator::markUseAsWrite(const Expr *E) {
+ auto *DRE = dyn_cast<DeclRefExpr>(E);
+ if (!DRE)
+ return;
+ if (!isPointerLikeType(DRE->getType()))
return;
assert(UseFacts.contains(DRE));
UseFacts[DRE]->markAsWritten();
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index ad61a42c0eaeb..54e343fc2ee5e 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -10,6 +10,7 @@
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
@@ -70,4 +71,34 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
return isNormalAssignmentOperator(FD);
}
+template <typename T> static bool isRecordWithAttr(QualType Type) {
+ auto *RD = Type->getAsCXXRecordDecl();
+ if (!RD)
+ return false;
+ // Generally, if a primary template class declaration is annotated with an
+ // attribute, all its specializations generated from template instantiations
+ // should inherit the attribute.
+ //
+ // However, since lifetime analysis occurs during parsing, we may encounter
+ // cases where a full definition of the specialization is not required. In
+ // such cases, the specialization declaration remains incomplete and lacks the
+ // attribute. Therefore, we fall back to checking the primary template class.
+ //
+ // Note: it is possible for a specialization declaration to have an attribute
+ // even if the primary template does not.
+ //
+ // FIXME: What if the primary template and explicit specialization
+ // declarations have conflicting attributes? We should consider diagnosing
+ // this scenario.
+ bool Result = RD->hasAttr<T>();
+
+ if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+ Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr<T>();
+
+ return Result;
+}
+
+bool isGslPointerType(QualType QT) { return isRecordWithAttr<PointerAttr>(QT); }
+bool isGslOwnerType(QualType QT) { return isRecordWithAttr<OwnerAttr>(QT); }
+
} // namespace clang::lifetimes
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 59f594e50fb46..eb054a3cc6761 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -111,13 +111,19 @@ class AnalysisImpl
/// dominates this program point. A write operation kills the liveness of
/// the origin since it overwrites the value.
Lattice transfer(Lattice In, const UseFact &UF) {
- OriginID OID = UF.getUsedOrigin();
- // Write kills liveness.
- if (UF.isWritten())
- return Lattice(Factory.remove(In.LiveOrigins, OID));
- // Read makes origin live with definite confidence (dominates this point).
- return Lattice(Factory.add(In.LiveOrigins, OID,
- LivenessInfo(&UF, LivenessKind::Must)));
+ Lattice Out = In;
+ for (OriginID OID : UF.getUsedOrigins()) {
+ // Write kills liveness.
+ if (UF.isWritten()) {
+ Out = Lattice(Factory.remove(Out.LiveOrigins, OID));
+ } else {
+ // Read makes origin live with definite confidence (dominates this
+ // point).
+ Out = Lattice(Factory.add(Out.LiveOrigins, OID,
+ LivenessInfo(&UF, LivenessKind::Must)));
+ }
+ }
+ return Out;
}
/// Issuing a new loan to an origin kills its liveness.
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index 0e6c194123df8..4511572215bd6 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -62,7 +62,8 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
CheckOrigin(F->getAs<ReturnOfOriginFact>()->getReturnedOriginID());
break;
case Fact::Kind::Use:
- CheckOrigin(F->getAs<UseFact>()->getUsedOrigin());
+ for (OriginID OID : F->getAs<UseFact>()->getUsedOrigins())
+ CheckOrigin(OID);
break;
case Fact::Kind::Expire:
case Fact::Kind::TestPoint:
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 0f2eaa94a5987..279e406da2de0 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -7,70 +7,102 @@
//===----------------------------------------------------------------------===//
#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 {
+bool isPointerLikeType(QualType QT) {
+ return QT->isPointerOrReferenceType() || isGslPointerType(QT);
+}
+
+static bool hasOrigins(QualType QT) { return isPointerLikeType(QT); }
+
+// TODO: Doc.
+// Rvalues of non-pointer type do not have origins.
+static bool hasOrigins(const Expr *E) {
+ return E->isGLValue() || hasOrigins(E->getType());
+}
+
void OriginManager::dump(OriginID OID, llvm::raw_ostream &OS) const {
OS << OID << " (";
Origin O = getOrigin(OID);
- if (const ValueDecl *VD = O.getDecl())
+ if (const ValueDecl *VD = O.getDecl()) {
OS << "Decl: " << VD->getNameAsString();
- else if (const Expr *E = O.getExpr())
+ } else if (const Expr *E = O.getExpr()) {
OS << "Expr: " << E->getStmtClassName();
- else
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ if (const ValueDecl *VD = DRE->getDecl())
+ OS << "(" << VD->getNameAsString() << ")";
+ }
+ } else {
OS << "Unknown";
+ }
OS << ")";
}
-Origin &OriginManager::addOrigin(OriginID ID, const clang::ValueDecl &D) {
- AllOrigins.emplace_back(ID, &D);
- return AllOrigins.back();
-}
-
-Origin &OriginManager::addOrigin(OriginID ID, const clang::Expr &E) {
- AllOrigins.emplace_back(ID, &E);
- return AllOrigins.back();
+template <typename T>
+OriginTree *OriginManager::buildTreeForType(QualType QT, const T *Node) {
+ assert(isPointerLikeType(QT) &&
+ "buildTreeForType called for non-pointer type");
+ OriginTree *Root = createNode(Node);
+ 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 (isPointerLikeType(PointeeTy))
+ Root->Pointee = buildTreeForType(PointeeTy, Node);
+ }
+ return Root;
}
-// TODO: Mark this method as const once we remove the call to getOrCreate.
-OriginID OriginManager::get(const Expr &E) {
- if (auto *ParenIgnored = E.IgnoreParens(); ParenIgnored != &E)
- return get(*ParenIgnored);
- auto It = ExprToOriginID.find(&E);
- if (It != ExprToOriginID.end())
+OriginTree *OriginManager::getOrCreateTree(const ValueDecl *D) {
+ if (!isPointerLikeType(D->getType()))
+ return nullptr;
+ auto It = DeclToTree.find(D);
+ if (It != DeclToTree.end())
return It->second;
- // If the expression itself has no specific origin, and it's a reference
- // to a declaration, its origin is that of the declaration it refers to.
- // For pointer types, where we don't pre-emptively create an origin for the
- // DeclRefExpr itself.
- if (const auto *DRE = dyn_cast<DeclRefExpr>(&E))
- return get(*DRE->getDecl());
- // TODO: This should be an assert(It != ExprToOriginID.end()). The current
- // implementation falls back to getOrCreate to avoid crashing on
- // yet-unhandled pointer expressions, creating an empty origin for them.
- return getOrCreate(E);
+ return DeclToTree[D] = buildTreeForType(D->getType(), D);
}
-OriginID OriginManager::get(const ValueDecl &D) {
- auto It = DeclToOriginID.find(&D);
- // TODO: This should be an assert(It != DeclToOriginID.end()). The current
- // implementation falls back to getOrCreate to avoid crashing on
- // yet-unhandled pointer expressions, creating an empty origin for them.
- if (It == DeclToOriginID.end())
- return getOrCreate(D);
+OriginTree *OriginManager::getOrCreateTree(const Expr *E, ASTContext &Ctx) {
+ if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E)
+ return getOrCreateTree(ParenIgnored, Ctx);
- return It->second;
-}
+ if (!hasOrigins(E))
+ return nullptr;
-OriginID OriginManager::getOrCreate(const Expr &E) {
- auto It = ExprToOriginID.find(&E);
- if (It != ExprToOriginID.end())
+ auto It = ExprToTree.find(E);
+ if (It != ExprToTree.end())
return It->second;
- OriginID NewID = getNextOriginID();
- addOrigin(NewID, E);
- ExprToOriginID[&E] = NewID;
- return NewID;
+ QualType Type = E->getType();
+
+ // TODO: Doc about DeclRefExpr.
+ // Why does reference type does not
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ OriginTree *Root = nullptr;
+ if (DRE->getDecl()->getType()->isReferenceType())
+ Root = getOrCreateTree(DRE->getDecl());
+ else {
+ // Create outer origin. Use inner tree from the underlying decl.
+ // This also ensures that the underlying decl's origins are always reused!
+ Root = createNode(DRE);
+ Root->Pointee = getOrCreateTree(DRE->getDecl());
+ }
+ return ExprToTree[E] = Root;
+ }
+ // If E is an lvalue , it refers to storage. We model this storage as the
+ // first level of origin tree, as if it were a reference, because l-values are
+ // addressable.
+ if (E->isGLValue() && !Type->isReferenceType())
+ Type = Ctx.getLValueReferenceType(Type);
+ ExprToTree[E] = buildTreeForType(Type, E);
+ return ExprToTree[E];
}
const Origin &OriginManager::getOrigin(OriginID ID) const {
@@ -78,14 +110,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/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index f9665b5e59831..c91ca751984c9 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -17,6 +17,9 @@
#include "llvm/ADT/PointerIntPair.h"
namespace clang::sema {
+using lifetimes::isGslOwnerType;
+using lifetimes::isGslPointerType;
+
namespace {
enum LifetimeKind {
/// The lifetime of a temporary bound to this entity ends at the end of the
@@ -257,38 +260,8 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
Expr *Init, ReferenceKind RK,
LocalVisitor Visit);
-template <typename T> static bool isRecordWithAttr(QualType Type) {
- auto *RD = Type->getAsCXXRecordDecl();
- if (!RD)
- return false;
- // Generally, if a primary template class declaration is annotated with an
- // attribute, all its specializations generated from template instantiations
- // should inherit the attribute.
- //
- // However, since lifetime analysis occurs during parsing, we may encounter
- // cases where a full definition of the specialization is not required. In
- // such cases, the specialization declaration remains incomplete and lacks the
- // attribute. Therefore, we fall back to checking the primary template class.
- //
- // Note: it is possible for a specialization declaration to have an attribute
- // even if the primary template does not.
- //
- // FIXME: What if the primary template and explicit specialization
- // declarations have conflicting attributes? We should consider diagnosing
- // this scenario.
- bool Result = RD->hasAttr<T>();
-
- if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
- Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr<T>();
-
- return Result;
-}
-
-// Tells whether the type is annotated with [[gsl::Pointer]].
-bool isGLSPointerType(QualType QT) { return isRecordWithAttr<PointerAttr>(QT); }
-
static bool isPointerLikeType(QualType QT) {
- return isGLSPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
+ return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
}
// Decl::isInStdNamespace will return false for iterators in some STL
@@ -331,7 +304,7 @@ static bool isContainerOfOwner(const RecordDecl *Container) {
return false;
const auto &TAs = CTSD->getTemplateArgs();
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
- isRecordWithAttr<OwnerAttr>(TAs[0].getAsType());
+ isGslOwnerType(TAs[0].getAsType());
}
// Returns true if the given Record is `std::initializer_list<pointer>`.
@@ -349,14 +322,13 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
- if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()) &&
+ if (isGslPointerType(Conv->getConversionType()) &&
Callee->getParent()->hasAttr<OwnerAttr>())
return true;
if (!isInStlNamespace(Callee->getParent()))
return false;
- if (!isRecordWithAttr<PointerAttr>(
- Callee->getFunctionObjectParameterType()) &&
- !isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
+ if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
+ !isGslOwnerType(Callee->getFunctionObjectParameterType()))
return false;
if (isPointerLikeType(Callee->getReturnType())) {
if (!Callee->getIdentifier())
@@ -393,7 +365,7 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
return false;
if (FD->getReturnType()->isPointerType() ||
- isRecordWithAttr<PointerAttr>(FD->getReturnType())) {
+ isGslPointerType(FD->getReturnType())) {
return llvm::StringSwitch<bool>(FD->getName())
.Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
.Cases({"end", "rend", "cend", "crend"}, true)
@@ -465,7 +437,7 @@ shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
return true;
// RHS must be an owner.
- if (!isRecordWithAttr<OwnerAttr>(RHSArgType))
+ if (!isGslOwnerType(RHSArgType))
return false;
// Bail out if the RHS is Owner<Pointer>.
@@ -547,7 +519,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
// Once we initialized a value with a non gsl-owner reference, it can no
// longer dangle.
if (ReturnType->isReferenceType() &&
- !isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
+ !isGslOwnerType(ReturnType->getPointeeType())) {
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
@@ -1158,8 +1130,7 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
// auto p2 = Temp().owner; // Here p2 is dangling.
if (const auto *FD = llvm::dyn_cast_or_null<FieldDecl>(E.D);
FD && !FD->getType()->isReferenceType() &&
- isRecordWithAttr<OwnerAttr>(FD->getType()) &&
- LK != LK_MemInitializer) {
+ isGslOwnerType(FD->getType()) && LK != LK_MemInitializer) {
return Report;
}
return Abandon;
@@ -1191,10 +1162,9 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
// const GSLOwner& func(const Foo& foo [[clang::lifetimebound]])
// GSLOwner* func(cosnt Foo& foo [[clang::lifetimebound]])
// GSLPointer func(const Foo& foo [[clang::lifetimebound]])
- if (FD &&
- ((FD->getReturnType()->isPointerOrReferenceType() &&
- isRecordWithAttr<OwnerAttr>(FD->getReturnType()->getPointeeType())) ||
- isGLSPointerType(FD->getReturnType())))
+ if (FD && ((FD->getReturnType()->isPointerOrReferenceType() &&
+ isGslOwnerType(FD->getReturnType()->getPointeeType())) ||
+ isGslPointerType(FD->getReturnType())))
return Report;
return Abandon;
@@ -1206,7 +1176,7 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
// int &p = *localUniquePtr;
// someContainer.add(std::move(localUniquePtr));
// return p;
- if (!pathContainsInit(Path) && isRecordWithAttr<OwnerAttr>(L->getType()))
+ if (!pathContainsInit(Path) && isGslOwnerType(L->getType()))
return Report;
return Abandon;
}
@@ -1215,8 +1185,7 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
bool IsGslPtrValueFromGslTempOwner =
- MTE && !MTE->getExtendingDecl() &&
- isRecordWithAttr<OwnerAttr>(MTE->getType());
+ MTE && !MTE->getExtendingDecl() && isGslOwnerType(MTE->getType());
// Skipping a chain of initializing gsl::Pointer annotated objects.
// We are looking only for the final source to find out if it was
// a local or temporary owner or the address of a local
@@ -1231,7 +1200,7 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
return (EnableGSLAssignmentWarnings &&
- (isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
+ (isGslPointerType(Entity.LHS->getType()) ||
lifetimes::isAssignmentOperatorLifetimeBound(
Entity.AssignmentOperator)));
}
@@ -1400,7 +1369,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
// Suppress false positives for code like the one below:
// Ctor(unique_ptr<T> up) : pointer(up.get()), owner(move(up)) {}
// FIXME: move this logic to analyzePathForGSLPointer.
- if (DRE && isRecordWithAttr<OwnerAttr>(DRE->getType()))
+ if (DRE && isGslOwnerType(DRE->getType()))
return false;
auto *VD = DRE ? dyn_cast<VarDecl>(DRE->getDecl()) : nullptr;
diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h
index 16595d0ca1b36..38b7061988dc7 100644
--- a/clang/lib/Sema/CheckExprLifetime.h
+++ b/clang/lib/Sema/CheckExprLifetime.h
@@ -18,9 +18,6 @@
namespace clang::sema {
-// Tells whether the type is annotated with [[gsl::Pointer]].
-bool isGLSPointerType(QualType QT);
-
/// Describes an entity that is being assigned.
struct AssignedEntity {
// The left-hand side expression of the assignment.
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 8411a3da8322d..7729c113e422e 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -11,11 +11,11 @@
//
//===----------------------------------------------------------------------===//
-#include "CheckExprLifetime.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Lookup.h"
@@ -289,7 +289,7 @@ void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) {
// We only apply the lifetime_capture_by attribute to parameters of
// pointer-like reference types (`const T&`, `T&&`).
if (PVD->getType()->isReferenceType() &&
- sema::isGLSPointerType(PVD->getType().getNonReferenceType())) {
+ lifetimes::isGslPointerType(PVD->getType().getNonReferenceType())) {
int CaptureByThis[] = {LifetimeCaptureByAttr::This};
PVD->addAttr(
LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1));
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index b9368db550805..d8b166336ea0f 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 -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 {
@@ -220,6 +224,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;
@@ -444,13 +458,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;
{
@@ -497,25 +504,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() {
@@ -556,18 +572,18 @@ void lifetimebound_partial_safety(bool cond) {
v.use(); // expected-note {{later used here}}
}
-// FIXME: Creating reference from lifetimebound call doesn't propagate loans.
+// TODO:Extensively test references types.
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;
- }
- (void)*ptr;
+ } // expected-note {{destroyed here}}
+ (void)*ptr; // expected-note {{later used here}}
}
// FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types.
@@ -575,6 +591,7 @@ struct LifetimeBoundCtor {
LifetimeBoundCtor();
LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
};
+
void lifetimebound_ctor() {
LifetimeBoundCtor v;
{
@@ -686,3 +703,67 @@ void parentheses(bool cond) {
} // expected-note 4 {{destroyed here}}
(void)*p; // expected-note 4 {{later used here}}
}
+
+namespace GH162834 {
+template <class T>
+struct StatusOr {
+ ~StatusOr() {}
+ const T& value() const& [[clang::lifetimebound]] { return data; }
+
+ private:
+ T data;
+};
+
+StatusOr<View> getViewOr();
+StatusOr<MyObj> getStringOr();
+StatusOr<MyObj*> getPointerOr();
+
+void foo() {
+ View view;
+ {
+ StatusOr<View> view_or = getViewOr();
+ view = view_or.value();
+ }
+ (void)view;
+}
+
+void bar() {
+ MyObj* pointer;
+ {
+ StatusOr<MyObj*> pointer_or = getPointerOr();
+ pointer = pointer_or.value();
+ }
+ (void)*pointer;
+}
+
+void foobar() {
+ View view;
+ {
+ StatusOr<MyObj> string_or = getStringOr();
+ view = string_or. // expected-warning {{object whose reference is captured does not live long enough}}
+ value();
+ } // expected-note {{destroyed here}}
+ (void)view; // expected-note {{later used here}}
+}
+} // namespace GH162834
+
+namespace 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 601308c53f9a9..b63ddbb7367ac 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -101,8 +101,9 @@ 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);
+ // return const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr())
+ // .get(*VD);
+ return std::nullopt;
}
std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
More information about the cfe-commits
mailing list