[clang] [LifetimeSafety] Warn on implicit this lifetimebound violations (PR #196926)
via cfe-commits
cfe-commits at lists.llvm.org
Mon May 11 04:12:20 PDT 2026
https://github.com/NeKon69 created https://github.com/llvm/llvm-project/pull/196926
With this change we report `[[clang::lifetimebound]]` violations on the implicit `this` parameter.
It also adds a helper to retrieve the `[[clang::lifetimebound]]` attribute on method declarations, so diagnostics can point directly at the attribute location.
>From a476f6283ccd236f58b85ca2956b1db4d905439b Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 11 May 2026 12:48:36 +0300
Subject: [PATCH 1/4] [LifetimeSafety] Warn on implicit this lifetimebound
violations
---
.../Analyses/LifetimeSafety/LifetimeSafety.h | 8 +++-
.../clang/Basic/DiagnosticSemaKinds.td | 4 +-
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 13 ++++--
clang/lib/Sema/SemaLifetimeSafety.h | 17 ++++++-
.../warn-lifetime-safety-lifetimebound.cpp | 45 +++++++++++++++++++
5 files changed, 78 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 7ccf30ba14987..b4f918a4cb3e7 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -110,7 +110,13 @@ class LifetimeSafetySemaHelper {
// Reports misuse of [[clang::lifetimebound]] when parameter doesn't escape
// through return.
- virtual void reportLifetimeboundViolation(const ParmVarDecl *VD) {}
+ virtual void
+ reportLifetimeboundViolation(const ParmVarDecl *ParmWithLifetimebound) {}
+
+ // Reports misuse of [[clang::lifetimebound]] when implicit this parameter
+ // doesn't escape through return.
+ virtual void
+ reportLifetimeboundViolation(const CXXMethodDecl *MDWithLifetimebound) {}
// Suggests lifetime bound annotations for implicit this.
virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 879812f3de0d3..4374ca7235d03 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11007,8 +11007,8 @@ def warn_lifetime_safety_dangling_global_moved
InGroup<LifetimeSafetyDanglingGlobalMoved>,
DefaultIgnore;
-def warn_lifetime_safety_param_lifetimebound_violation
- : Warning<"could not verify that the return value can be lifetime bound to %select{an unnamed parameter|'%1'}0">,
+def warn_lifetime_safety_lifetimebound_violation
+ : Warning<"could not verify that the return value can be lifetime bound to %select{an unnamed parameter|'%1'|an implicit this parameter}0">,
InGroup<LifetimeSafetyLifetimeboundViolation>,
DefaultIgnore;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index fc77ed3097602..f60448b23e6e8 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -60,7 +60,7 @@ class LifetimeChecker {
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap;
llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
- llvm::DenseSet<const ParmVarDecl *> VerifiedLiftimeboundEscapes;
+ llvm::DenseSet<const Decl *> VerifiedLiftimeboundEscapes;
const LoanPropagationAnalysis &LoanPropagation;
const MovedLoansAnalysis &MovedLoans;
const LiveOriginsAnalysis &LiveOrigins;
@@ -147,9 +147,10 @@ class LifetimeChecker {
// field!
};
auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
- if (!implicitObjectParamIsLifetimeBound(MD))
- if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
- AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
+ if (implicitObjectParamIsLifetimeBound(MD))
+ VerifiedLiftimeboundEscapes.insert(MD);
+ else if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+ AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
};
auto MovedAtEscape = MovedLoans.getMovedLoans(OEF);
for (LoanID LID : EscapedLoans) {
@@ -366,6 +367,10 @@ class LifetimeChecker {
void reportLifetimeboundViolations() {
if (!isa<FunctionDecl>(FD))
return;
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ MD && implicitObjectParamIsLifetimeBound(MD) &&
+ !VerifiedLiftimeboundEscapes.contains(MD))
+ SemaHelper->reportLifetimeboundViolation(MD);
for (const ParmVarDecl *PVD : cast<FunctionDecl>(FD)->parameters()) {
if (!PVD->hasAttr<LifetimeBoundAttr>())
continue;
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 5b1cf41445399..f104ea5cb0026 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -36,7 +36,7 @@ inline bool IsLifetimeSafetyDiagnosticEnabled(Sema &S, const Decl *D) {
diag::warn_lifetime_safety_dangling_global,
diag::warn_lifetime_safety_dangling_global_moved,
diag::warn_lifetime_safety_noescape_escapes,
- diag::warn_lifetime_safety_param_lifetimebound_violation,
+ diag::warn_lifetime_safety_lifetimebound_violation,
};
for (unsigned DiagID : DiagIDs)
if (!Diags.isIgnored(DiagID, D->getBeginLoc()))
@@ -183,10 +183,23 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
StringRef ParamName = ParmWithLifetimebound->getName();
bool HasName = ParamName.size() > 0;
S.Diag(ParmWithLifetimebound->getLocation(),
- diag::warn_lifetime_safety_param_lifetimebound_violation)
+ diag::warn_lifetime_safety_lifetimebound_violation)
<< HasName << ParamName << ParmWithLifetimebound->getSourceRange();
}
+ void reportLifetimeboundViolation(
+ const CXXMethodDecl *MDWithLifetimebound) override {
+ const Stmt *Body = MDWithLifetimebound->getBody();
+ assert(Body && "Expected a body");
+ // FIXME: When #196549 lands, we can extract the attribute location and warn
+ // on it, for now warn on everything before the body.
+ S.Diag(MDWithLifetimebound->getLocation(),
+ diag::warn_lifetime_safety_lifetimebound_violation)
+ << 2 << "this"
+ << CharSourceRange::getCharRange(MDWithLifetimebound->getBeginLoc(),
+ Body->getBeginLoc());
+ }
+
void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
const CXXMethodDecl *MD,
const Expr *EscapeExpr) override {
diff --git a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
index 941a3bb8ce1e3..a7caa1e773fe2 100644
--- a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
@@ -87,3 +87,48 @@ View annotated_decl_but_not_def_not_returned(const MyObj &obj [[clang::lifetimeb
View annotated_decl_but_not_def_not_returned(const MyObj &obj) { // expected-warning {{could not verify that the return value can be lifetime bound to 'obj'}}
return not_lb(obj);
}
+
+struct BadThisReturn {
+ MyObj data;
+
+ View get() const [[clang::lifetimebound]] { // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
+ return not_lb(data);
+ }
+};
+
+struct GoodThisReturn {
+ MyObj data;
+
+ View get() const [[clang::lifetimebound]] {
+ return data;
+ }
+};
+
+// FIXME: Wrong warning loc
+struct RedeclaredThis {
+ MyObj data;
+ View get() const [[clang::lifetimebound]];
+};
+
+View RedeclaredThis::get() const { // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
+ return not_lb(data);
+}
+
+struct ThisAndParam {
+ MyObj data;
+
+ View get(const MyObj &obj [[clang::lifetimebound]]) const [[clang::lifetimebound]] { // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
+ return lb(obj);
+ }
+};
+
+struct ThisAndMixedParams {
+ MyObj data;
+
+ View get( // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
+ const MyObj &a [[clang::lifetimebound]],
+ const MyObj &b,
+ const MyObj &c [[clang::lifetimebound]]) const [[clang::lifetimebound]] { // expected-warning {{could not verify that the return value can be lifetime bound to 'c'}}
+ return cond() ? lb(a) : not_lb(b);
+ }
+};
>From 137b72977b159012989a2ab161e24f7146d37f04 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 11 May 2026 13:55:43 +0300
Subject: [PATCH 2/4] warn on attribute loc instead of def in implicit this
lifetimbound
---
.../LifetimeSafety/LifetimeAnnotations.h | 5 ++++
.../LifetimeSafety/LifetimeAnnotations.cpp | 28 ++++++++++++-------
clang/lib/Sema/SemaLifetimeSafety.h | 14 ++++------
.../warn-lifetime-safety-lifetimebound.cpp | 9 +++---
4 files changed, 34 insertions(+), 22 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 0db10f8a58cea..f418f8a5132ec 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -41,6 +41,11 @@ bool isNormalAssignmentOperator(const FunctionDecl *FD);
/// has the lifetimebound attribute.
bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
+/// Returns the lifetimebound attribute for the implicit this parameter, if it
+/// exists on any redeclaration.
+const LifetimeBoundAttr *
+getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD);
+
/// Returns true if the implicit object parameter (this) should be considered
/// lifetimebound, either due to an explicit lifetimebound attribute on the
/// method or because it's a normal assignment operator.
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 559188fddc9fa..b4ae00f2b0327 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -72,21 +72,29 @@ getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) {
return nullptr;
}
-bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
+const LifetimeBoundAttr *
+getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD) {
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
// Attribute merging doesn't work well with attributes on function types (like
// 'this' param). We need to check all redeclarations.
- auto CheckRedecls = [](const FunctionDecl *F) {
- return llvm::any_of(F->redecls(), [](const FunctionDecl *Redecl) {
- const TypeSourceInfo *TSI = Redecl->getTypeSourceInfo();
- return TSI && getLifetimeBoundAttrFromFunctionType(*TSI);
- });
+ auto CheckRedecls = [](const FunctionDecl *F) -> const LifetimeBoundAttr * {
+ for (const FunctionDecl *Redecl : F->redecls()) {
+ if (const TypeSourceInfo *TSI = Redecl->getTypeSourceInfo())
+ if (const auto *Attr = getLifetimeBoundAttrFromFunctionType(*TSI))
+ return Attr;
+ }
+ return nullptr;
};
- if (CheckRedecls(FD))
- return true;
- if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern();
- Pattern && CheckRedecls(Pattern))
+ if (const auto *Attr = CheckRedecls(FD))
+ return Attr;
+ if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern())
+ return CheckRedecls(Pattern);
+ return nullptr;
+}
+
+bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
+ if (getImplicitObjectParamLifetimeBoundAttr(FD))
return true;
return isNormalAssignmentOperator(FD);
}
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index f104ea5cb0026..8ea172d1a9624 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -15,6 +15,7 @@
#ifndef LLVM_CLANG_LIB_SEMA_SEMALIFETIMESAFETY_H
#define LLVM_CLANG_LIB_SEMA_SEMALIFETIMESAFETY_H
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Lex/Lexer.h"
@@ -189,15 +190,12 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
void reportLifetimeboundViolation(
const CXXMethodDecl *MDWithLifetimebound) override {
- const Stmt *Body = MDWithLifetimebound->getBody();
- assert(Body && "Expected a body");
- // FIXME: When #196549 lands, we can extract the attribute location and warn
- // on it, for now warn on everything before the body.
- S.Diag(MDWithLifetimebound->getLocation(),
+ const auto *Attr =
+ getImplicitObjectParamLifetimeBoundAttr(MDWithLifetimebound);
+ assert(Attr && "Expected lifetimebound attribute");
+ S.Diag(Attr->getLocation(),
diag::warn_lifetime_safety_lifetimebound_violation)
- << 2 << "this"
- << CharSourceRange::getCharRange(MDWithLifetimebound->getBeginLoc(),
- Body->getBeginLoc());
+ << 2 << "" << Attr->getRange();
}
void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
diff --git a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
index a7caa1e773fe2..1e27091099d04 100644
--- a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
@@ -107,10 +107,10 @@ struct GoodThisReturn {
// FIXME: Wrong warning loc
struct RedeclaredThis {
MyObj data;
- View get() const [[clang::lifetimebound]];
+ View get() const [[clang::lifetimebound]]; // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
};
-View RedeclaredThis::get() const { // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
+View RedeclaredThis::get() const {
return not_lb(data);
}
@@ -125,10 +125,11 @@ struct ThisAndParam {
struct ThisAndMixedParams {
MyObj data;
- View get( // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
+ View get(
const MyObj &a [[clang::lifetimebound]],
const MyObj &b,
- const MyObj &c [[clang::lifetimebound]]) const [[clang::lifetimebound]] { // expected-warning {{could not verify that the return value can be lifetime bound to 'c'}}
+ const MyObj &c [[clang::lifetimebound]]) const [[clang::lifetimebound]] { // expected-warning {{could not verify that the return value can be lifetime bound to 'c'}} \
+ // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
return cond() ? lb(a) : not_lb(b);
}
};
>From 69be295afb3e080f0be4418dd775507ff6c9db61 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 11 May 2026 13:57:32 +0300
Subject: [PATCH 3/4] revert unrelated change
---
.../clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index b4f918a4cb3e7..c3b33fefe8327 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -110,8 +110,7 @@ class LifetimeSafetySemaHelper {
// Reports misuse of [[clang::lifetimebound]] when parameter doesn't escape
// through return.
- virtual void
- reportLifetimeboundViolation(const ParmVarDecl *ParmWithLifetimebound) {}
+ virtual void reportLifetimeboundViolation(const ParmVarDecl *VD) {}
// Reports misuse of [[clang::lifetimebound]] when implicit this parameter
// doesn't escape through return.
>From 47b05b4e627d7af64eac53e33b952c7c2decc2b7 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 11 May 2026 13:59:08 +0300
Subject: [PATCH 4/4] remove fixme
---
clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
index 1e27091099d04..4aeac3ae21f77 100644
--- a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
@@ -104,7 +104,6 @@ struct GoodThisReturn {
}
};
-// FIXME: Wrong warning loc
struct RedeclaredThis {
MyObj data;
View get() const [[clang::lifetimebound]]; // expected-warning {{could not verify that the return value can be lifetime bound to an implicit this parameter}}
More information about the cfe-commits
mailing list