[llvm-branch-commits] [clang] do this only for lifetime analysis (PR #176794)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jan 19 10:11:30 PST 2026
https://github.com/usx95 created https://github.com/llvm/llvm-project/pull/176794
None
>From db325fd8d1035a164766f5ea04bb389f8e65981b Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 19 Jan 2026 18:10:47 +0000
Subject: [PATCH] do this only for lifetime analysis
---
.../LifetimeSafety/LifetimeAnnotations.h | 3 +-
.../LifetimeSafety/FactsGenerator.cpp | 6 ++--
.../LifetimeSafety/LifetimeAnnotations.cpp | 15 +++++---
clang/lib/Sema/CheckExprLifetime.cpp | 3 +-
clang/test/Sema/Inputs/lifetime-analysis.h | 6 ++++
.../Sema/warn-lifetime-analysis-nocfg.cpp | 34 +++++++++++++++++--
6 files changed, 56 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 760d34d33b15b..3ca7a79d32ee1 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -51,7 +51,8 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
// 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);
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
+ bool RunningUnderLifetimeSafety);
// 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
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 5d1afd15c795d..bbab0627bdead 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -501,7 +501,8 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
if (I == 0)
// For the 'this' argument, the attribute is on the method itself.
return implicitObjectParamIsLifetimeBound(Method) ||
- shouldTrackImplicitObjectArg(Method);
+ shouldTrackImplicitObjectArg(
+ Method, /*RunningUnderLifetimeSafety=*/true);
if ((I - 1) < Method->getNumParams())
// For explicit arguments, find the corresponding parameter
// declaration.
@@ -520,7 +521,8 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
return false;
return I == 0 &&
isGslPointerType(Method->getFunctionObjectParameterType()) &&
- shouldTrackImplicitObjectArg(Method);
+ shouldTrackImplicitObjectArg(Method,
+ /*RunningUnderLifetimeSafety=*/true);
};
if (Args.empty())
return;
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index c380543e52a98..93c7a86d0a57c 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -100,7 +100,8 @@ bool isPointerLikeType(QualType QT) {
return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
}
-bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
+ bool RunningUnderLifetimeSafety) {
if (!Callee)
return false;
if (auto *Conv = dyn_cast<CXXConversionDecl>(Callee))
@@ -113,10 +114,14 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
!isGslOwnerType(Callee->getFunctionObjectParameterType()))
return false;
- // Track dereference operator for GSL pointers in STL.
- if (isGslPointerType(Callee->getFunctionObjectParameterType()))
- if (Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Star)
- return true;
+ // Track dereference operator for GSL pointers in STL. Only do so for lifetime
+ // safety analysis and not for Sema's statement-local analysis as it starts
+ // to have false-positives.
+ if (RunningUnderLifetimeSafety &&
+ isGslPointerType(Callee->getFunctionObjectParameterType()) &&
+ (Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Star ||
+ Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Arrow))
+ return true;
if (isPointerLikeType(Callee->getReturnType())) {
if (!Callee->getIdentifier())
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 0b2f00f417337..5d4b4508541b7 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -482,7 +482,8 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
VisitLifetimeBoundArg(Callee, ObjectArg);
else if (EnableGSLAnalysis) {
if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
- CME && lifetimes::shouldTrackImplicitObjectArg(CME))
+ CME && lifetimes::shouldTrackImplicitObjectArg(
+ CME, /*RunningUnderLifetimeSafety=*/false))
VisitGSLPointerArg(Callee, ObjectArg);
}
}
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index b47fe78bdf0fb..4f881d463ebcc 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -58,6 +58,12 @@ struct vector {
void insert(iterator, T&&);
};
+template<typename A, typename B>
+struct pair {
+ A first;
+ B second;
+};
+
template<typename T>
struct basic_string_view {
basic_string_view();
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index f4d175981619f..5b6a548389bd5 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -986,8 +986,38 @@ std::string_view test_opt_strings(std::optional<std::vector<std::string>> string
namespace iterator_arrow {
std::string_view test() {
std::vector<std::string> strings;
- // FIXME: Track operator-> of iterators.
- return strings.begin()->data();
+ return strings.begin()->data(); // cfg-warning {{address of stack memory is returned later}} cfg-note {{returned here}}
+}
+
+void operator_star_arrow_reference() {
+ std::vector<std::string> v;
+ const char* p = v.begin()->data();
+ const char* q = (*v.begin()).data();
+ const std::string& r = *v.begin();
+
+ auto temporary = []() { return std::vector<std::string>{{"1"}}; };
+ const char* x = temporary().begin()->data(); // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ const char* y = (*temporary().begin()).data(); // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ const std::string& z = (*temporary().begin()); // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+
+ use(p, q, r, x, y, z); // cfg-note 3 {{later used here}}
+}
+
+void operator_star_arrow_of_iterators_false_positive_no_cfg_analysis() {
+ std::vector<std::pair<int, std::string>> v;
+ const char* p = v.begin()->second.data();
+ const char* q = (*v.begin()).second.data();
+ const std::string& r = (*v.begin()).second;
+
+ // FIXME: Detect this using the CFG-based lifetime analysis.
+ // Detect dangling references to struct field.
+ // https://github.com/llvm/llvm-project/issues/176144
+ auto temporary = []() { return std::vector<std::pair<int, std::string>>{{1, "1"}}; };
+ const char* x = temporary().begin()->second.data();
+ const char* y = (*temporary().begin()).second.data();
+ const std::string& z = (*temporary().begin()).second;
+
+ use(p, q, r, x, y, z);
}
} // namespace iterator_arrow
More information about the llvm-branch-commits
mailing list