[llvm-branch-commits] [clang] Implicit lifetimebound for std namespace (PR #170005)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Nov 29 09:11:23 PST 2025
https://github.com/usx95 created https://github.com/llvm/llvm-project/pull/170005
None
>From 363a78cc719fcd2d96ab3805ed58b08de52cbf82 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 29 Nov 2025 15:07:40 +0000
Subject: [PATCH] Implicit lifetimebound for std namespace
---
.../LifetimeSafety/LifetimeAnnotations.h | 4 +
.../LifetimeSafety/FactsGenerator.cpp | 3 +-
.../LifetimeSafety/LifetimeAnnotations.cpp | 82 +++++++++++++++++++
clang/lib/Sema/CheckExprLifetime.cpp | 64 +--------------
4 files changed, 90 insertions(+), 63 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 1a16fb82f9a84..65ff03d48094d 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -38,6 +38,10 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
/// method or because it's a normal assignment operator.
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee);
+
+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 d2ef4248cb8c0..52a74bf40d8a0 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -395,7 +395,8 @@ 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.
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/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)) {
More information about the llvm-branch-commits
mailing list