[clang] [LifetimeSafety] Warn on inapplicable `[[clang::lifetimebound]]` when return type can not carry a lifetime (PR #203767)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 14 06:22:25 PDT 2026
https://github.com/NeKon69 created https://github.com/llvm/llvm-project/pull/203767
Extends `-Wlifetime-safety-inapplicable-lifetimebound` to also diagnose when the function's return type cannot carry a lifetime, making `[[clang::lifetimebound]]` on parameters/implicit this inapplicable.
The attribute is only meaningful when the return value can refer to another object. When the return type is `int`, `void`, `Owner`, or any other type that cannot carry a lifetime, the attribute has no effect.
For constructors, we use the class type as the effective return type instead of `void`, so `gsl::Pointer` constructors still work correctly. However, this means existing constructors with `clang::lifetimebound` on plain/mixed types now get diagnosed.
Closes #188655
Assisted-by: Kimi K2.6 for writing some of the tests.
>From a5b932706518c1513894400307801e359ac619fe Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 14 Jun 2026 16:11:12 +0300
Subject: [PATCH] [LifetimeSafety] Warn on inapplicable
[[clang::lifetimebound]] when return type can not carry a lifetime
---
.../Analyses/LifetimeSafety/LifetimeSafety.h | 5 +++-
.../clang/Basic/DiagnosticSemaKinds.td | 5 ++++
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 23 +++++++++++++---
clang/lib/Sema/SemaLifetimeSafety.h | 17 +++++++++---
.../LifetimeSafety/annotation-suggestions.cpp | 6 ++---
.../inapplicable-lifetimebound.cpp | 27 ++++++++++++++++++-
.../misplaced-lifetimebound-intra-tu.cpp | 2 +-
clang/test/Sema/LifetimeSafety/safety.cpp | 8 +++---
8 files changed, 76 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 28886b826f72f..ce35a14641a15 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -142,7 +142,10 @@ class LifetimeSafetySemaHelper {
const ParmVarDecl *PVDDef,
const ParmVarDecl *PVDDecl) {}
- virtual void reportInapplicableLifetimebound(const ParmVarDecl *PVD) {}
+ virtual void reportInapplicableLifetimebound(const ParmVarDecl *PVD,
+ QualType Type,
+ bool IsReturnType) {}
+ virtual void reportInapplicableLifetimebound(const CXXMethodDecl *MD) {}
// Suggests lifetime bound annotations for implicit this.
virtual void suggestLifetimeboundToImplicitThis(WarningScope Scope,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8097800e6744a..30ab502c32000 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11048,6 +11048,11 @@ def warn_lifetime_safety_inapplicable_lifetimebound
InGroup<LifetimeSafetyInapplicableLifetimebound>,
DefaultIgnore;
+def warn_lifetime_safety_inapplicable_lifetimebound_return
+ : Warning<"'lifetimebound' attribute has no effect on this function because return type %0 cannot carry a lifetime. The attribute only applies when the return type is a reference type, pointer type, or non-owner record type">,
+ InGroup<LifetimeSafetyInapplicableLifetimebound>,
+ DefaultIgnore;
+
def note_lifetime_safety_used_here : Note<"later used here">;
def note_lifetime_safety_invalidated_here : Note<"invalidated here">;
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index d41d6f43f837b..41945607cf531 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -484,11 +484,26 @@ class LifetimeChecker {
FDef->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
return;
+ QualType ReturnType = FDef->getReturnType();
+ if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(FDef))
+ ReturnType = AST.getTypeDeclType(cast<TypeDecl>(Ctor->getParent()));
+
+ bool ReturnTypeHasOrigins =
+ FactMgr.getOriginMgr().hasOrigins(ReturnType,
+ /*IntrinsicOnly=*/true);
+ if (getImplicitObjectParamLifetimeBoundAttr(FDef) && !ReturnTypeHasOrigins)
+ SemaHelper->reportInapplicableLifetimebound(cast<CXXMethodDecl>(FDef));
+
for (const auto &PVD : FDef->parameters())
- if (PVD->hasAttr<LifetimeBoundAttr>() &&
- !FactMgr.getOriginMgr().hasOrigins(PVD->getType(),
- /*IntrinsicOnly=*/true))
- SemaHelper->reportInapplicableLifetimebound(PVD);
+ if (PVD->hasAttr<LifetimeBoundAttr>()) {
+ if (!FactMgr.getOriginMgr().hasOrigins(PVD->getType(),
+ /*IntrinsicOnly=*/true))
+ SemaHelper->reportInapplicableLifetimebound(PVD, PVD->getType(),
+ /*IsReturnType=*/false);
+ if (!ReturnTypeHasOrigins)
+ SemaHelper->reportInapplicableLifetimebound(PVD, ReturnType,
+ /*IsReturnType=*/true);
+ }
}
void inferAnnotations() {
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 91f1290f38723..20312d1787c87 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -353,13 +353,24 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
<< Attr->getRange();
}
- void reportInapplicableLifetimebound(const ParmVarDecl *PVD) override {
+ void reportInapplicableLifetimebound(const ParmVarDecl *PVD, QualType Type,
+ bool IsReturnType) override {
assert(PVD->hasAttr<LifetimeBoundAttr>() &&
"Expected parameter to have lifetimebound attribute");
const auto *Attr = PVD->getAttr<LifetimeBoundAttr>();
+ unsigned DiagID =
+ IsReturnType
+ ? diag::warn_lifetime_safety_inapplicable_lifetimebound_return
+ : diag::warn_lifetime_safety_inapplicable_lifetimebound;
+ S.Diag(Attr->getLocation(), DiagID) << Type << Attr->getRange();
+ }
+
+ void reportInapplicableLifetimebound(const CXXMethodDecl *MD) override {
+ const auto *Attr = getImplicitObjectParamLifetimeBoundAttr(MD);
+ assert(Attr && "Expected lifetimebound attribute");
S.Diag(Attr->getLocation(),
- diag::warn_lifetime_safety_inapplicable_lifetimebound)
- << PVD->getType() << Attr->getRange();
+ diag::warn_lifetime_safety_inapplicable_lifetimebound_return)
+ << MD->getReturnType() << Attr->getRange();
}
void suggestLifetimeboundToImplicitThis(WarningScope Scope,
diff --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
index cef3397b57a6f..754533b065836 100644
--- a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
+++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
@@ -1,7 +1,7 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wlifetime-safety-annotation-placement -Wno-dangling -I%t -I%S -verify %t/test_source.cpp
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -verify %t/test_source.cpp
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wlifetime-safety-annotation-placement -Wno-dangling -I%t -I%S -verify %t/test_source.cpp
// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -fixit %t/test_source.cpp
// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%t -I%S -Werror=lifetime-safety-suggestions %t/test_source.cpp
@@ -615,7 +615,7 @@ namespace make_unique_suggestion {
struct LifetimeBoundCtor {
View v;
// FIXME: This test fails to propagate the lifetimebound in ctor if this is inferred (instead of the current explicit annotation).
- LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]): v(obj) {}
+ LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]): v(obj) {} // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'make_unique_suggestion::LifetimeBoundCtor' cannot carry a lifetime}}
};
std::unique_ptr<LifetimeBoundCtor> create_target(const MyObj& obj) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
@@ -649,7 +649,7 @@ void test_new_allocation() {
struct LifetimeBoundCtor {
View v;
LifetimeBoundCtor();
- LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]) : v(obj) {}
+ LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]) : v(obj) {} // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'new_allocation_suggestion::LifetimeBoundCtor' cannot carry a lifetime}}
};
struct HasCtorField {
diff --git a/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp b/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp
index 624a8e9bb00cc..0e8f3196df972 100644
--- a/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp
+++ b/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp
@@ -9,7 +9,14 @@ struct [[gsl::Pointer()]] View {
View(const Owner &o [[clang::lifetimebound]]);
};
-struct Plain {};
+struct Plain {
+ Plain() {}
+ Plain(const Plain &other [[clang::lifetimebound]]) {} // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'Plain' cannot carry a lifetime}}
+ Plain operator+(const Plain &other [[clang::lifetimebound]]) const { return {}; } // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'Plain' cannot carry a lifetime}}
+ Plain &operator=(const Plain &other [[clang::lifetimebound]]) { return *this; }
+ operator int() const [[clang::lifetimebound]] { return 0; } // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'int' cannot carry a lifetime}}
+ Plain(const Owner &o [[clang::lifetimebound]]) {} // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'Plain' cannot carry a lifetime}}
+};
enum Enum { Enumerator };
@@ -90,3 +97,21 @@ int *context_sensitive_origin_type(
lifetime_annotated_return(i);
return v[0];
}
+
+int getInt(const Owner &o [[clang::lifetimebound]]) { return 0; } // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'int' cannot carry a lifetime}}
+Owner getOwner(const Owner &o [[clang::lifetimebound]]) { return o; } // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'Owner' cannot carry a lifetime}}
+Plain getPlain(const Owner &o [[clang::lifetimebound]]) { return {}; } // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'Plain' cannot carry a lifetime}}
+
+View getView(const Owner &o [[clang::lifetimebound]]) { return View(); }
+Owner *getOwnerPtr(Owner &o [[clang::lifetimebound]]) { return &o; }
+
+auto getAuto(const Owner &o [[clang::lifetimebound]]) { return 0; } // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'int' cannot carry a lifetime}}
+
+int getIntMultiParam(const Owner &o [[clang::lifetimebound]], int i [[clang::lifetimebound]]) { return 0; } // expected-warning 2 {{'lifetimebound' attribute has no effect on this function because return type 'int' cannot carry a lifetime}} \
+ // expected-warning {{'lifetimebound' attribute has no effect on parameter of type 'int'}}
+
+void test_lambda() {
+ auto lambda = [](const Owner &o [[clang::lifetimebound]]) -> int { return 0; }; // expected-warning {{'lifetimebound' attribute has no effect on this function because return type 'int' cannot carry a lifetime}}
+ (void)lambda;
+}
+
diff --git a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
index 7fa4cae100509..b1cfb490446e0 100644
--- a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
+++ b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
@@ -119,7 +119,7 @@ IntraSuppressedObj &intra_suppressed(
return obj;
}
-struct View {
+struct [[gsl::Pointer()]] View {
friend View friend_redecl(MyObj & // expected-warning {{'lifetimebound' attribute on this definition is not visible to callers before the definition; add it to the declaration instead}}
obj // CHECK: fix-it:"{{.*}}":{[[@LINE]]:{{[0-9]+}}-[[@LINE]]:{{[0-9]+}}}:" {{\[\[clang::lifetimebound\]\]}}"
);
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index c838918eb556d..687aa4c962b6c 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -1,6 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wlifetime-safety-annotation-placement -Wno-dangling -verify=expected,function %s
// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=expected,tu %s
-// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -fcxx-exceptions -verify=expected,function %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wlifetime-safety-annotation-placement -Wno-dangling -fcxx-exceptions -verify=expected,function %s
#include "Inputs/lifetime-analysis.h"
@@ -2533,7 +2533,7 @@ S S::return_self_after_registration() const {
struct SWithUserDefinedCopyLikeOps {
SWithUserDefinedCopyLikeOps();
- SWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]) : owned(s), data(s) {}
+ SWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]) : owned(s), data(s) {} // function-warning {{'lifetimebound' attribute has no effect on this function because return type 'track_origins_for_lifetimebound_record_type::SWithUserDefinedCopyLikeOps' cannot carry a lifetime}}
SWithUserDefinedCopyLikeOps(const SWithUserDefinedCopyLikeOps &other) : owned("copy"), data(owned) {}
@@ -2586,7 +2586,7 @@ void from_typedef_return() {
struct SWithOriginPropagatingCopy {
SWithOriginPropagatingCopy();
- SWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]) : data(s) {}
+ SWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]) : data(s) {} // function-warning {{'lifetimebound' attribute has no effect on this function because return type 'track_origins_for_lifetimebound_record_type::SWithOriginPropagatingCopy' cannot carry a lifetime}}
SWithOriginPropagatingCopy(const SWithOriginPropagatingCopy &other) : data(other.data) {}
std::string_view data;
};
@@ -2603,7 +2603,7 @@ SWithOriginPropagatingCopy user_defined_copy_with_origin_propagation() {
struct DefaultedOuter {
DefaultedOuter();
- DefaultedOuter(const std::string &s [[clang::lifetimebound]]) : inner(s) {}
+ DefaultedOuter(const std::string &s [[clang::lifetimebound]]) : inner(s) {} // function-warning {{'lifetimebound' attribute has no effect on this function because return type 'track_origins_for_lifetimebound_record_type::DefaultedOuter' cannot carry a lifetime}}
SWithUserDefinedCopyLikeOps inner;
};
More information about the cfe-commits
mailing list