https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/154009
>From afa47aa95a56474c5a1f0163d9b6120df10f22a8 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Sun, 17 Aug 2025 10:10:18 +0000 Subject: [PATCH] [LifetimeSafety] Track gsl::Pointer types --- clang/lib/Analysis/LifetimeSafety.cpp | 158 +++++++++++++++--- .../unittests/Analysis/LifetimeSafetyTest.cpp | 153 ++++++++++++++++- 2 files changed, 290 insertions(+), 21 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp index 9397c530a9af2..24ec3298ded71 100644 --- a/clang/lib/Analysis/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -428,6 +428,31 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> { addAssignOriginFact(*VD, *InitExpr); } + void VisitCXXConstructExpr(const CXXConstructExpr *CCE) { + if (!isGslPointerType(CCE->getType())) + return; + + if (CCE->getNumArgs() > 0 && hasOrigin(CCE->getArg(0)->getType())) + // This is a propagation. + addAssignOriginFact(*CCE, *CCE->getArg(0)); + else + // This could be a new borrow. + checkForBorrows(CCE, CCE->getConstructor(), + {CCE->getArgs(), CCE->getNumArgs()}); + } + + void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { + if (!isGslPointerType(MCE->getImplicitObjectArgument()->getType())) + return; + // Specifically for conversion operators, like `std::string_view p = a;` + if (isa<CXXConversionDecl>(MCE->getCalleeDecl())) { + // The argument is the implicit object itself. + checkForBorrows(MCE, MCE->getMethodDecl(), + {MCE->getImplicitObjectArgument()}); + } + // Note: A more general VisitCallExpr could also be used here. + } + void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) { /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized /// pointers can use the same type of loan. @@ -479,20 +504,13 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> { } void VisitBinaryOperator(const BinaryOperator *BO) { - if (BO->isAssignmentOp()) { - const Expr *LHSExpr = BO->getLHS(); - const Expr *RHSExpr = BO->getRHS(); - - // We are interested in assignments like `ptr1 = ptr2` or `ptr = &var` - // LHS must be a pointer/reference type that can be an origin. - // RHS must also represent an origin (either another pointer/ref or an - // address-of). - if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) - if (const auto *VD_LHS = - dyn_cast<ValueDecl>(DRE_LHS->getDecl()->getCanonicalDecl()); - VD_LHS && hasOrigin(VD_LHS->getType())) - addAssignOriginFact(*VD_LHS, *RHSExpr); - } + if (BO->isAssignmentOp()) + handleAssignment(BO->getLHS(), BO->getRHS()); + } + + void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { + if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) + handleAssignment(OCE->getArg(0), OCE->getArg(1)); } void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) { @@ -500,8 +518,25 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> { // expression. if (VisitTestPoint(FCE)) return; - // Visit as normal otherwise. - Base::VisitCXXFunctionalCastExpr(FCE); + if (isGslPointerType(FCE->getType())) + addAssignOriginFact(*FCE, *FCE->getSubExpr()); + } + + void VisitInitListExpr(const InitListExpr *ILE) { + if (!hasOrigin(ILE->getType())) + return; + // For list initialization with a single element, like `View{...}`, the + // origin of the list itself is the origin of its single element. + if (ILE->getNumInits() == 1) + addAssignOriginFact(*ILE, *ILE->getInit(0)); + } + + void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) { + if (!hasOrigin(MTE->getType())) + return; + // A temporary object's origin is the same as the origin of the + // expression that initializes it. + addAssignOriginFact(*MTE, *MTE->getSubExpr()); } void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) { @@ -527,8 +562,88 @@ class FactGeneratorVisitor : public ConstStmtVisitor<FactGeneratorVisitor> { } private: + static bool isGslPointerType(QualType QT) { + if (const auto *RD = QT->getAsCXXRecordDecl()) { + // We need to check the template definition for specializations. + if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) + return CTSD->getSpecializedTemplate() + ->getTemplatedDecl() + ->hasAttr<PointerAttr>(); + return RD->hasAttr<PointerAttr>(); + } + return false; + } + // Check if a type has an origin. - bool hasOrigin(QualType QT) { return QT->isPointerOrReferenceType(); } + static bool hasOrigin(QualType QT) { + if (QT->isFunctionPointerType()) + return false; + return QT->isPointerOrReferenceType() || isGslPointerType(QT); + } + + /// Checks if a call-like expression creates a borrow by passing a local + /// value to a reference parameter, creating an IssueFact if it does. + void checkForBorrows(const Expr *Call, const FunctionDecl *FD, + ArrayRef<const Expr *> Args) { + if (!FD) + return; + + for (unsigned I = 0; I < Args.size(); ++I) { + if (I >= FD->getNumParams()) + break; + + const ParmVarDecl *Param = FD->getParamDecl(I); + const Expr *Arg = Args[I]; + + // This is the core condition for a new borrow: a value type (no origin) + // is passed to a reference parameter. + if (Param->getType()->isReferenceType() && !hasOrigin(Arg->getType())) { + if (const Loan *L = createLoanFrom(Arg, Call)) { + OriginID OID = FactMgr.getOriginMgr().getOrCreate(*Call); + CurrentBlockFacts.push_back( + FactMgr.createFact<IssueFact>(L->ID, OID)); + // For view creation, we assume the first borrow is the significant + // one. + return; + } + } + } + } + + /// Attempts to create a loan by analyzing the source expression of a borrow. + /// This method is the single point for creating loans, allowing for future + /// expansion to handle temporaries, field members, etc. + /// \param SourceExpr The expression representing the object being borrowed + /// from. + /// \param IssueExpr The expression that triggers the borrow (e.g., a + /// constructor call). + /// \return The new Loan on success, nullptr on failure. + const Loan *createLoanFrom(const Expr *SourceExpr, const Expr *IssueExpr) { + // For now, we only handle direct borrows from local variables. + // In the future, this can be extended to handle MaterializeTemporaryExpr, + // etc. + if (const auto *DRE = + dyn_cast<DeclRefExpr>(SourceExpr->IgnoreParenImpCasts())) { + if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) { + AccessPath Path(VD); + return &FactMgr.getLoanMgr().addLoan(Path, IssueExpr); + } + } + return nullptr; + } + + void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr) { + // Find the underlying variable declaration for the left-hand side. + if (const auto *DRE_LHS = + dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) + if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) + if (hasOrigin(VD_LHS->getType())) + // We are interested in assignments like `ptr1 = ptr2` or `ptr = &var` + // LHS must be a pointer/reference type that can be an origin. + // RHS must also represent an origin (either another pointer/ref or an + // address-of). + addAssignOriginFact(*VD_LHS, *RHSExpr); + } template <typename Destination, typename Source> void addAssignOriginFact(const Destination &D, const Source &S) { @@ -578,10 +693,13 @@ class FactGenerator : public RecursiveASTVisitor<FactGenerator> { for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) { FactGeneratorBlockRAII BlockGenerator(FG, Block); for (const CFGElement &Element : *Block) { - if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) + if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) { + DEBUG_WITH_TYPE("PrintCFG", llvm::dbgs() << " ================== \n"); + DEBUG_WITH_TYPE("PrintCFG", CS->dump()); + DEBUG_WITH_TYPE("PrintCFG", CS->getStmt()->dumpColor()); TraverseStmt(const_cast<Stmt *>(CS->getStmt())); - else if (std::optional<CFGAutomaticObjDtor> DtorOpt = - Element.getAs<CFGAutomaticObjDtor>()) + } else if (std::optional<CFGAutomaticObjDtor> DtorOpt = + Element.getAs<CFGAutomaticObjDtor>()) FG.handleDestructor(*DtorOpt); } } diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 13e5832d70050..0d6a03f9946fa 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -11,7 +11,6 @@ #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Testing/TestAST.h" #include "llvm/ADT/StringMap.h" -#include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include <optional> @@ -31,7 +30,13 @@ class LifetimeTestRunner { LifetimeTestRunner(llvm::StringRef Code) { std::string FullCode = R"( #define POINT(name) void("__lifetime_test_point_" #name) + struct MyObj { ~MyObj() {} int i; }; + + struct [[gsl::Pointer()]] View { + View(const MyObj&); + View(); + }; )"; FullCode += Code.str(); @@ -741,5 +746,151 @@ TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) { EXPECT_THAT(Helper->getLoansForVar("a"), SizeIs(2)); } +TEST_F(LifetimeAnalysisTest, GslPointerSimpleLoan) { + SetupTest(R"( + void target() { + MyObj a; + View x = a; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, GslPointerConstructFromOwner) { + SetupTest(R"( + void target() { + MyObj al, bl, cl, dl, el; + View a = View(al); + View b = View{bl}; + View c = View(View(View(cl))); + View d = View{View(View(dl))}; + View e = View{View{View{el}}}; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("a"), HasLoansTo({"al"}, "p1")); + EXPECT_THAT(Origin("b"), HasLoansTo({"bl"}, "p1")); + EXPECT_THAT(Origin("c"), HasLoansTo({"cl"}, "p1")); + EXPECT_THAT(Origin("d"), HasLoansTo({"dl"}, "p1")); + EXPECT_THAT(Origin("e"), HasLoansTo({"el"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, GslPointerConstructFromView) { + SetupTest(R"( + void target() { + MyObj a; + View x = View(a); + View y = View{x}; + View z = View(View(View(y))); + View p = View{View(View(x))}; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1")); +} + +// FIXME: Handle loans in ternary operator! +TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) { + SetupTest(R"( + void target(bool cond) { + MyObj a, b; + View v = cond ? a : b; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1")); +} + +// FIXME: Handle temporaries. +TEST_F(LifetimeAnalysisTest, ViewFromTemporary) { + SetupTest(R"( + MyObj temporary(); + void target() { + View v = temporary(); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) { + SetupTest(R"( + void target() { + MyObj a; + const View v1 = a; + auto v2 = v1; + const auto& v3 = v2; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, GslPointerPropagation) { + SetupTest(R"( + void target() { + MyObj a; + View x = a; + POINT(p1); + + View y = x; // Propagation via copy-construction + POINT(p2); + + View z; + z = x; // Propagation via copy-assignment + POINT(p3); + } + )"); + + EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1")); + EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p2")); + EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p3")); +} + +TEST_F(LifetimeAnalysisTest, GslPointerLoanExpiration) { + SetupTest(R"( + void target() { + View x; + { + MyObj a; + x = a; + POINT(before_expiry); + } // `a` is destroyed here. + POINT(after_expiry); + } + )"); + + EXPECT_THAT(NoLoans(), AreExpiredAt("before_expiry")); + EXPECT_THAT(LoansTo({"a"}), AreExpiredAt("after_expiry")); +} + +TEST_F(LifetimeAnalysisTest, GslPointerReassignment) { + SetupTest(R"( + void target() { + MyObj safe; + View v; + v = safe; + POINT(p1); + { + MyObj unsafe; + v = unsafe; + POINT(p2); + } // `unsafe` expires here. + POINT(p3); + } + )"); + + EXPECT_THAT(Origin("v"), HasLoansTo({"safe"}, "p1")); + EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p2")); + EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p3")); + EXPECT_THAT(LoansTo({"unsafe"}), AreExpiredAt("p3")); +} + } // anonymous namespace } // namespace clang::lifetimes::internal _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits