https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/176794
>From 1729d1ad8e823ccd30ea44d2437697fed8b4b25e Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
