https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/170005
>From a71af86558edcaeb1876f0ba3550b4ef24c05a65 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 29 Nov 2025 15:07:40 +0000 Subject: [PATCH] Implicit lifetimebound for std namespace --- .../Analyses/LifetimeSafety/FactsGenerator.h | 1 + .../LifetimeSafety/LifetimeAnnotations.h | 14 ++ .../LifetimeSafety/FactsGenerator.cpp | 19 +- .../LifetimeSafety/LifetimeAnnotations.cpp | 82 ++++++++ clang/lib/Analysis/LifetimeSafety/Origins.cpp | 3 + clang/lib/Sema/CheckExprLifetime.cpp | 64 +------ .../unittests/Analysis/LifetimeSafetyTest.cpp | 181 ++++++++++++++++++ 7 files changed, 298 insertions(+), 66 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 5b5626020e772..d3ef72a8d12dd 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -48,6 +48,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE); void VisitInitListExpr(const InitListExpr *ILE); void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE); + void VisitExprWithCleanups(const ExprWithCleanups *EC); private: OriginList *getOriginsList(const ValueDecl &D); diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index 1a16fb82f9a84..8e26a4d41a957 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -38,6 +38,20 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD); /// method or because it's a normal assignment operator. bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD); +// Returns true if the implicit object argument (this) of a method call should +// be tracked for GSL lifetime analysis. This applies to STL methods that return +// pointers or references that depend on the lifetime of the object, such as +// container iterators (begin, end), data accessors (c_str, data, get), or +// element accessors (operator[], operator*, front, back, at). +bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee); + +// Returns true if the first argument of a free function should be tracked for +// GSL lifetime analysis. This applies to STL free functions that take a pointer +// to a GSL Owner or Pointer and return a pointer or reference that depends on +// the lifetime of the argument, such as std::begin, std::data, std::get, or +// std::any_cast. +bool shouldTrackFirstArgument(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]]. diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 460be3ad99347..6883d31c4a12b 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -14,6 +14,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" #include "clang/Analysis/Analyses/PostOrderCFGView.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Debug.h" #include "llvm/Support/Signals.h" #include "llvm/Support/TimeProfiler.h" @@ -78,10 +79,11 @@ void FactsGenerator::run() { EscapesInCurrentBlock.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>()) { Visit(CS->getStmt()); - else if (std::optional<CFGLifetimeEnds> LifetimeEnds = - Element.getAs<CFGLifetimeEnds>()) + DEBUG_WITH_TYPE("PrintCFG", CS->getStmt()->dumpColor()); + } else if (std::optional<CFGLifetimeEnds> LifetimeEnds = + Element.getAs<CFGLifetimeEnds>()) handleLifetimeEnds(*LifetimeEnds); } CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), @@ -328,6 +330,12 @@ void FactsGenerator::VisitMaterializeTemporaryExpr( } } +void FactsGenerator::VisitExprWithCleanups(const ExprWithCleanups *EC) { + if (hasOrigins(EC)) { + killAndFlowOrigin(*EC, *EC->getSubExpr()); + } +} + void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) { /// TODO: Handle loans to temporaries. const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl(); @@ -387,11 +395,14 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, Method && Method->isInstance()) { if (I == 0) // For the 'this' argument, the attribute is on the method itself. - return implicitObjectParamIsLifetimeBound(Method); + return implicitObjectParamIsLifetimeBound(Method) || + shouldTrackImplicitObjectArg(Method); if ((I - 1) < Method->getNumParams()) // For explicit arguments, find the corresponding parameter // declaration. PVD = Method->getParamDecl(I - 1); + } else if (I == 0 && shouldTrackFirstArgument(FD)) { + return true; } else if (I < FD->getNumParams()) { // For free functions or static methods. PVD = FD->getParamDecl(I); diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 54e343fc2ee5e..860aa5373a32c 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -71,6 +71,88 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) { return isNormalAssignmentOperator(FD); } +// Decl::isInStdNamespace will return false for iterators in some STL +// implementations due to them being defined in a namespace outside of the std +// namespace. +static bool isInStlNamespace(const Decl *D) { + const DeclContext *DC = D->getDeclContext(); + if (!DC) + return false; + if (const auto *ND = dyn_cast<NamespaceDecl>(DC)) + if (const IdentifierInfo *II = ND->getIdentifier()) { + StringRef Name = II->getName(); + if (Name.size() >= 2 && Name.front() == '_' && + (Name[1] == '_' || isUppercase(Name[1]))) + return true; + } + + return DC->isStdNamespace(); +} + +static bool isPointerLikeType(QualType QT) { + return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType(); +} + +bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) { + if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee)) + if (isGslPointerType(Conv->getConversionType()) && + Callee->getParent()->hasAttr<OwnerAttr>()) + return true; + if (!isInStlNamespace(Callee->getParent())) + return false; + if (!isGslPointerType(Callee->getFunctionObjectParameterType()) && + !isGslOwnerType(Callee->getFunctionObjectParameterType())) + return false; + if (isPointerLikeType(Callee->getReturnType())) { + if (!Callee->getIdentifier()) + return false; + return llvm::StringSwitch<bool>(Callee->getName()) + .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true) + .Cases({"end", "rend", "cend", "crend"}, true) + .Cases({"c_str", "data", "get"}, true) + // Map and set types. + .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true) + .Default(false); + } + if (Callee->getReturnType()->isReferenceType()) { + if (!Callee->getIdentifier()) { + auto OO = Callee->getOverloadedOperator(); + if (!Callee->getParent()->hasAttr<OwnerAttr>()) + return false; + return OO == OverloadedOperatorKind::OO_Subscript || + OO == OverloadedOperatorKind::OO_Star; + } + return llvm::StringSwitch<bool>(Callee->getName()) + .Cases({"front", "back", "at", "top", "value"}, true) + .Default(false); + } + return false; +} + +bool shouldTrackFirstArgument(const FunctionDecl *FD) { + if (!FD->getIdentifier() || FD->getNumParams() != 1) + return false; + const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl(); + if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace()) + return false; + if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>()) + return false; + if (FD->getReturnType()->isPointerType() || + isGslPointerType(FD->getReturnType())) { + return llvm::StringSwitch<bool>(FD->getName()) + .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true) + .Cases({"end", "rend", "cend", "crend"}, true) + .Case("data", true) + .Default(false); + } + if (FD->getReturnType()->isReferenceType()) { + return llvm::StringSwitch<bool>(FD->getName()) + .Cases({"get", "any_cast"}, true) + .Default(false); + } + return false; +} + template <typename T> static bool isRecordWithAttr(QualType Type) { auto *RD = Type->getAsCXXRecordDecl(); if (!RD) diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index ac8d8041f600b..65fc0c21e11b8 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -11,6 +11,7 @@ #include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/TypeBase.h" #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" @@ -79,6 +80,8 @@ OriginList *OriginManager::getOrCreateList(const ValueDecl *D) { OriginList *OriginManager::getOrCreateList(const Expr *E, size_t Depth) { if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E) return getOrCreateList(ParenIgnored); + if (auto *EC = dyn_cast<ExprWithCleanups>(E)) + return getOrCreateList(EC->getSubExpr(), Depth); if (!hasOrigins(E)) return nullptr; diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index c91ca751984c9..26e4d75b1fa49 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -320,66 +320,6 @@ static bool isStdInitializerListOfPointer(const RecordDecl *RD) { return false; } -static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) { - if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee)) - if (isGslPointerType(Conv->getConversionType()) && - Callee->getParent()->hasAttr<OwnerAttr>()) - return true; - if (!isInStlNamespace(Callee->getParent())) - return false; - if (!isGslPointerType(Callee->getFunctionObjectParameterType()) && - !isGslOwnerType(Callee->getFunctionObjectParameterType())) - return false; - if (isPointerLikeType(Callee->getReturnType())) { - if (!Callee->getIdentifier()) - return false; - return llvm::StringSwitch<bool>(Callee->getName()) - .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true) - .Cases({"end", "rend", "cend", "crend"}, true) - .Cases({"c_str", "data", "get"}, true) - // Map and set types. - .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true) - .Default(false); - } - if (Callee->getReturnType()->isReferenceType()) { - if (!Callee->getIdentifier()) { - auto OO = Callee->getOverloadedOperator(); - if (!Callee->getParent()->hasAttr<OwnerAttr>()) - return false; - return OO == OverloadedOperatorKind::OO_Subscript || - OO == OverloadedOperatorKind::OO_Star; - } - return llvm::StringSwitch<bool>(Callee->getName()) - .Cases({"front", "back", "at", "top", "value"}, true) - .Default(false); - } - return false; -} - -static bool shouldTrackFirstArgument(const FunctionDecl *FD) { - if (!FD->getIdentifier() || FD->getNumParams() != 1) - return false; - const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl(); - if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace()) - return false; - if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>()) - return false; - if (FD->getReturnType()->isPointerType() || - isGslPointerType(FD->getReturnType())) { - return llvm::StringSwitch<bool>(FD->getName()) - .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true) - .Cases({"end", "rend", "cend", "crend"}, true) - .Case("data", true) - .Default(false); - } - if (FD->getReturnType()->isReferenceType()) { - return llvm::StringSwitch<bool>(FD->getName()) - .Cases({"get", "any_cast"}, true) - .Default(false); - } - return false; -} - // Returns true if the given constructor is a copy-like constructor, such as // `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`. static bool isCopyLikeConstructor(const CXXConstructorDecl *Ctor) { @@ -564,7 +504,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call, VisitLifetimeBoundArg(Callee, ObjectArg); else if (EnableGSLAnalysis) { if (auto *CME = dyn_cast<CXXMethodDecl>(Callee); - CME && shouldTrackImplicitObjectArg(CME)) + CME && lifetimes::shouldTrackImplicitObjectArg(CME)) VisitGSLPointerArg(Callee, ObjectArg); } } @@ -605,7 +545,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call, VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg); else if (EnableGSLAnalysis && I == 0) { // Perform GSL analysis for the first argument - if (shouldTrackFirstArgument(CanonCallee)) { + if (lifetimes::shouldTrackFirstArgument(CanonCallee)) { VisitGSLPointerArg(CanonCallee, Arg); } else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call); Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) { diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index ddc9cb602fc26..553354f052875 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -1597,5 +1597,186 @@ TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) { EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); } +// ========================================================================= // +// Tests for shouldTrackImplicitObjectArg +// ========================================================================= // + +TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_STLBegin) { + SetupTest(R"( + namespace std { + template<typename T> + struct vector { + struct iterator {}; + iterator begin(); + }; + } + + void target() { + std::vector<int> vec; + auto it = vec.begin(); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1")); +} + + +TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_OwnerDeref) { + SetupTest(R"( + namespace std { + template<typename T> + struct optional { + T& operator*(); + }; + } + + void target() { + std::optional<int> opt; + int& r = *opt; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_Value) { + SetupTest(R"( + namespace std { + template<typename T> + struct optional { + T& value(); + }; + } + + void target() { + std::optional<int> opt; + int& r = opt.value(); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_UniquePtr_Get) { + SetupTest(R"( + namespace std { + template<typename T> + struct unique_ptr { + T *get() const; + }; + } + + void target() { + std::unique_ptr<int> up; + int* r = up.get(); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("r"), HasLoansTo({"up"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_ConversionOperator) { + SetupTest(R"( + struct [[gsl::Pointer(int)]] IntPtr { + int& operator*(); + }; + + struct [[gsl::Owner(int)]] OwnerWithConversion { + operator IntPtr(); + }; + + void target() { + OwnerWithConversion owner; + IntPtr ptr = owner; + POINT(p1); + } + )"); + EXPECT_THAT(Origin("ptr"), HasLoansTo({"owner"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_MapFind) { + SetupTest(R"( + namespace std { + template<typename K, typename V> + struct map { + struct iterator {}; + iterator find(const K&); + }; + } + + void target() { + std::map<int, int> m; + auto it = m.find(42); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("it"), HasLoansTo({"m"}, "p1")); +} + +// ========================================================================= // +// Tests for shouldTrackFirstArgument +// ========================================================================= // + +TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdBegin) { + SetupTest(R"( + namespace std { + template<typename T> + struct vector { + struct iterator {}; + iterator begin(); + }; + + template<typename C> + auto begin(C& c) -> decltype(c.begin()); + } + + void target() { + std::vector<int> vec; + auto it = std::begin(vec); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdData) { + SetupTest(R"( + namespace std { + template<typename T> + struct vector { + const T* data() const; + }; + + template<typename C> + auto data(C& c) -> decltype(c.data()); + } + + void target() { + std::vector<int> vec; + const int* p = std::data(vec); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("p"), HasLoansTo({"vec"}, "p1")); +} + +TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdAnyCast) { + SetupTest(R"( + namespace std { + struct any {}; + + template<typename T> + T any_cast(const any& op); + } + + void target() { + std::any a; + int& r = std::any_cast<int&>(a); + POINT(p1); + } + )"); + EXPECT_THAT(Origin("r"), HasLoansTo({"a"}, "p1")); +} + } // anonymous namespace } // namespace clang::lifetimes::internal _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
