https://github.com/NeKon69 updated 
https://github.com/llvm/llvm-project/pull/196144

>From 61b80f15e64db8cdbc4ef6cc813d8d2af8cb60d8 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Sun, 3 May 2026 21:31:39 +0300
Subject: [PATCH 01/11] basic impl

---
 .../Analyses/LifetimeSafety/LifetimeSafety.h      |  2 ++
 clang/include/clang/Basic/DiagnosticGroups.td     |  9 ++++++++-
 clang/include/clang/Basic/DiagnosticSemaKinds.td  |  5 +++++
 clang/lib/Analysis/LifetimeSafety/Checker.cpp     | 15 +++++++++++++++
 clang/lib/Sema/SemaLifetimeSafety.h               |  7 +++++++
 clang/test/Sema/warn-lifetime-safety.cpp          |  4 ++--
 6 files changed, 39 insertions(+), 3 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index d20ac87a7c8d9..6e352ee50e011 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -108,6 +108,8 @@ class LifetimeSafetySemaHelper {
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                        const VarDecl *EscapeGlobal) {}
 
+  virtual void reportLifetimeboundViolation(const ParmVarDecl *VD) {}
+
   // Suggests lifetime bound annotations for implicit this.
   virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
                                                   const CXXMethodDecl *MD,
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 2b3055d6d6bdd..b454b68a7f5ac 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -624,12 +624,19 @@ Warning to detect invalidation of references.
   }];
 }
 
+def LifetimeSafetyLifetimeboundViolation : 
DiagGroup<"lifetime-safety-lifetimebound-violation"> {
+  code Documentation = [{
+Warning to detect lifetimebound violations introduced by marking parameter as 
lifetimebound but not actually returning it.
+  }];
+}
+
 def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
                                          [LifetimeSafetyUseAfterScope,
                                          LifetimeSafetyReturnStackAddr,
                                          LifetimeSafetyDanglingField,
                                          LifetimeSafetyDanglingGlobal,
-                                         LifetimeSafetyUseAfterFree]>;
+                                         LifetimeSafetyUseAfterFree,
+                                         
LifetimeSafetyLifetimeboundViolation]>;
 
 def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
                                     [LifetimeSafetyPermissive,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 1302c4296885b..40409c0174470 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11003,6 +11003,11 @@ def warn_lifetime_safety_dangling_global_moved
       InGroup<LifetimeSafetyDanglingGlobalMoved>,
       DefaultIgnore;
 
+def warn_lifetime_safety_param_lifetimebound_violation
+    : Warning<"parameter is marked as [[clang::lifetimebound]] but doesn't 
escape">,
+      InGroup<LifetimeSafetyLifetimeboundViolation>,
+      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 4ae90cf751ec3..581e469d0e507 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -60,6 +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;
   const LoanPropagationAnalysis &LoanPropagation;
   const MovedLoansAnalysis &MovedLoans;
   const LiveOriginsAnalysis &LiveOrigins;
@@ -101,6 +102,7 @@ class LifetimeChecker {
     issuePendingWarnings();
     suggestAnnotations();
     reportNoescapeViolations();
+    reportLifetimeboundViolations();
     //  Annotation inference is currently guarded by a frontend flag. In the
     //  future, this might be replaced by a design that differentiates between
     //  explicit and inferred findings with separate warning groups.
@@ -137,6 +139,8 @@ class LifetimeChecker {
         else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF);
                  FieldEsc && isa<CXXConstructorDecl>(FD))
           AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
+      } else {
+        VerifiedLiftimeboundEscapes.insert(PVD);
       }
       // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
       // field!
@@ -358,6 +362,17 @@ class LifetimeChecker {
     }
   }
 
+  void reportLifetimeboundViolations() {
+    if (!isa<FunctionDecl>(FD))
+      return;
+    for (const ParmVarDecl *PVD : cast<FunctionDecl>(FD)->parameters()) {
+      if (!PVD->hasAttr<LifetimeBoundAttr>())
+        return;
+      if (!PVD->getAttr<LifetimeBoundAttr>()->isImplicit() &&
+          !VerifiedLiftimeboundEscapes.contains(PVD))
+        SemaHelper->reportLifetimeboundViolation(PVD);
+    }
+  }
   void inferAnnotations() {
     for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
       if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index 92e7b5cf14ae5..e900caadf1072 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -172,6 +172,13 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
           << EscapeField->getSourceRange();
   }
 
+  void reportLifetimeboundViolation(
+      const ParmVarDecl *ParmWithLifetimebound) override {
+    S.Diag(ParmWithLifetimebound->getLocation(),
+           diag::warn_lifetime_safety_param_lifetimebound_violation)
+        << ParmWithLifetimebound->getSourceRange();
+  }
+
   void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
                                           const CXXMethodDecl *MD,
                                           const Expr *EscapeExpr) override {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 30b450c333fbd..7cea7ba607f65 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3269,7 +3269,7 @@ void uaf_via_lifetimebound() {
 namespace GH126600 {
 struct [[gsl::Pointer]] function_ref {
   template <typename Callable>
-  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
+  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) 
{} // expected-warning {{parameter is marked as [[clang::lifetimebound]] but 
doesn't escape}}
   void (*ref)();
 };
 
@@ -3279,7 +3279,7 @@ struct [[gsl::Pointer]] function_ref {
 // avoid this warning for non-capturing lambdas.
 void assign_non_capturing_to_function_ref(function_ref &r) {
   r = []() {}; // expected-warning {{object whose reference is captured does 
not live long enough}} \
-               // expected-note {{destroyed here}}
+               // expected-note {{destroyed here}} function-note {{requested 
here}}
   (void)r; // expected-note {{later used here}}
 }
 

>From 9f78214e1528b634cbd8c3b21fe288fb75e19797 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 4 May 2026 16:47:28 +0300
Subject: [PATCH 02/11] cleanup code

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 581e469d0e507..a8f8a6ede4b6d 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -367,12 +367,13 @@ class LifetimeChecker {
       return;
     for (const ParmVarDecl *PVD : cast<FunctionDecl>(FD)->parameters()) {
       if (!PVD->hasAttr<LifetimeBoundAttr>())
-        return;
+        continue;
       if (!PVD->getAttr<LifetimeBoundAttr>()->isImplicit() &&
           !VerifiedLiftimeboundEscapes.contains(PVD))
         SemaHelper->reportLifetimeboundViolation(PVD);
     }
   }
+
   void inferAnnotations() {
     for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
       if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {

>From fc1bb8d414bd2ce96fdff73571a454b2c0d18fb6 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 4 May 2026 22:10:50 +0300
Subject: [PATCH 03/11] add tests

---
 clang/test/Sema/warn-lifetime-safety.cpp | 72 ++++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 7cea7ba607f65..5aaddafec6d07 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3284,3 +3284,75 @@ void assign_non_capturing_to_function_ref(function_ref 
&r) {
 }
 
 } // namespace GH126600
+
+namespace LifetimeboundReturnVerification {
+
+bool cond();
+
+View drop_lb(const MyObj &obj) { return obj; }
+
+View keep_lb(const MyObj &obj [[clang::lifetimebound]]) {
+  return obj;
+}
+
+View return_through_unannotated_passthrough(
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+  return drop_lb(obj);
+}
+
+View return_through_lifetimebound_passthrough(
+    const MyObj &obj [[clang::lifetimebound]]) {
+  return keep_lb(obj);
+}
+
+View keep_lb2(const MyObj &obj [[clang::lifetimebound]]) {
+  return keep_lb(obj);
+}
+
+View lose_lb(const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+  return drop_lb(obj);
+}
+
+View return_through_alias(const MyObj &obj [[clang::lifetimebound]]) {
+  const MyObj &alias = obj;
+  return alias;
+}
+
+View return_alias_through_unannotated_passthrough(
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+  const MyObj &alias = obj;
+  return drop_lb(alias);
+}
+
+View return_through_two_lifetimebound_calls(
+    const MyObj &obj [[clang::lifetimebound]]) {
+  return keep_lb2(obj);
+}
+
+View return_through_broken_chain(
+    const MyObj &obj [[clang::lifetimebound]]) {
+  return lose_lb(obj);
+}
+
+View fwd_view(View v) { return v; }
+
+View fwd_view_lb(View v [[clang::lifetimebound]]) { return v; }
+
+View return_constructed_view_through_unannotated_forwarder(
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+  return fwd_view(View(obj));
+}
+
+View return_constructed_view_through_lifetimebound_forwarder(
+    const MyObj &obj [[clang::lifetimebound]]) {
+  return fwd_view_lb(View(obj));
+}
+
+View verify_each_annotated_param_independently(
+    const MyObj &a [[clang::lifetimebound]],
+    const MyObj &b [[clang::lifetimebound]], // function-warning {{parameter 
is marked as [[clang::lifetimebound]] but doesn't escape}}
+    const MyObj &c [[clang::lifetimebound]]) { // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but doesn't escape}}
+  return cond() ? a : drop_lb(b);
+}
+
+} // namespace LifetimeboundReturnVerification

>From d7696dbfebf9b25e4d0712c3ea6fc66901be04cb Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 4 May 2026 22:46:00 +0300
Subject: [PATCH 04/11] fix tests

---
 clang/test/Sema/warn-lifetime-safety.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 5aaddafec6d07..a39b156930e3b 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3329,15 +3329,15 @@ View return_through_two_lifetimebound_calls(
   return keep_lb2(obj);
 }
 
-View return_through_broken_chain(
-    const MyObj &obj [[clang::lifetimebound]]) {
-  return lose_lb(obj);
-}
-
 View fwd_view(View v) { return v; }
 
 View fwd_view_lb(View v [[clang::lifetimebound]]) { return v; }
 
+View return_through_nested_broken_chain(
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+  return fwd_view(fwd_view_lb(View(obj)));
+}
+
 View return_constructed_view_through_unannotated_forwarder(
     const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
   return fwd_view(View(obj));

>From b8e90faf4162daffc98feb145af6248503e73183 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Wed, 6 May 2026 21:14:26 +0300
Subject: [PATCH 05/11] wording cleanup

---
 clang/include/clang/Basic/DiagnosticGroups.td    |  2 +-
 clang/include/clang/Basic/DiagnosticSemaKinds.td |  2 +-
 clang/test/Sema/warn-lifetime-safety.cpp         | 16 ++++++++--------
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index b454b68a7f5ac..c69fc8da20d87 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -626,7 +626,7 @@ Warning to detect invalidation of references.
 
 def LifetimeSafetyLifetimeboundViolation : 
DiagGroup<"lifetime-safety-lifetimebound-violation"> {
   code Documentation = [{
-Warning to detect lifetimebound violations introduced by marking parameter as 
lifetimebound but not actually returning it.
+Warning to detect lifetimebound violations introduced by marking parameter as 
lifetimebound but not returning it in any way.
   }];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 40409c0174470..ce9e00e4c48eb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11004,7 +11004,7 @@ def warn_lifetime_safety_dangling_global_moved
       DefaultIgnore;
 
 def warn_lifetime_safety_param_lifetimebound_violation
-    : Warning<"parameter is marked as [[clang::lifetimebound]] but doesn't 
escape">,
+    : Warning<"parameter is marked as [[clang::lifetimebound]] but isn't 
returned">,
       InGroup<LifetimeSafetyLifetimeboundViolation>,
       DefaultIgnore;
 
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index a39b156930e3b..6eb81995aaa4a 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3269,7 +3269,7 @@ void uaf_via_lifetimebound() {
 namespace GH126600 {
 struct [[gsl::Pointer]] function_ref {
   template <typename Callable>
-  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) 
{} // expected-warning {{parameter is marked as [[clang::lifetimebound]] but 
doesn't escape}}
+  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) 
{} // expected-warning {{parameter is marked as [[clang::lifetimebound]] but 
isn't returned}}
   void (*ref)();
 };
 
@@ -3296,7 +3296,7 @@ View keep_lb(const MyObj &obj [[clang::lifetimebound]]) {
 }
 
 View return_through_unannotated_passthrough(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
   return drop_lb(obj);
 }
 
@@ -3309,7 +3309,7 @@ View keep_lb2(const MyObj &obj [[clang::lifetimebound]]) {
   return keep_lb(obj);
 }
 
-View lose_lb(const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+View lose_lb(const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
   return drop_lb(obj);
 }
 
@@ -3319,7 +3319,7 @@ View return_through_alias(const MyObj &obj 
[[clang::lifetimebound]]) {
 }
 
 View return_alias_through_unannotated_passthrough(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
   const MyObj &alias = obj;
   return drop_lb(alias);
 }
@@ -3334,12 +3334,12 @@ View fwd_view(View v) { return v; }
 View fwd_view_lb(View v [[clang::lifetimebound]]) { return v; }
 
 View return_through_nested_broken_chain(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
   return fwd_view(fwd_view_lb(View(obj)));
 }
 
 View return_constructed_view_through_unannotated_forwarder(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but doesn't escape}}
+    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
   return fwd_view(View(obj));
 }
 
@@ -3350,8 +3350,8 @@ View 
return_constructed_view_through_lifetimebound_forwarder(
 
 View verify_each_annotated_param_independently(
     const MyObj &a [[clang::lifetimebound]],
-    const MyObj &b [[clang::lifetimebound]], // function-warning {{parameter 
is marked as [[clang::lifetimebound]] but doesn't escape}}
-    const MyObj &c [[clang::lifetimebound]]) { // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but doesn't escape}}
+    const MyObj &b [[clang::lifetimebound]], // function-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
+    const MyObj &c [[clang::lifetimebound]]) { // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
   return cond() ? a : drop_lb(b);
 }
 

>From 17c0508ebd4c70e40a16f87ad4c0bb6fddf7f879 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 8 May 2026 16:38:28 +0300
Subject: [PATCH 06/11] address review comments

---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  2 +
 clang/include/clang/Basic/DiagnosticGroups.td |  9 +--
 .../warn-lifetime-safety-lifetimebound.cpp    | 80 +++++++++++++++++++
 clang/test/Sema/warn-lifetime-safety.cpp      | 76 +-----------------
 4 files changed, 88 insertions(+), 79 deletions(-)
 create mode 100644 clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 6e352ee50e011..7ccf30ba14987 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -108,6 +108,8 @@ class LifetimeSafetySemaHelper {
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                        const VarDecl *EscapeGlobal) {}
 
+  // Reports misuse of [[clang::lifetimebound]] when parameter doesn't escape
+  // through return.
   virtual void reportLifetimeboundViolation(const ParmVarDecl *VD) {}
 
   // Suggests lifetime bound annotations for implicit this.
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index c69fc8da20d87..f4e0d58b7ba1b 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -626,7 +626,7 @@ Warning to detect invalidation of references.
 
 def LifetimeSafetyLifetimeboundViolation : 
DiagGroup<"lifetime-safety-lifetimebound-violation"> {
   code Documentation = [{
-Warning to detect lifetimebound violations introduced by marking parameter as 
lifetimebound but not returning it in any way.
+Detects parameters marked as [[clang::lifetimebound]] that do not escape 
through the return value.
   }];
 }
 
@@ -635,8 +635,7 @@ def LifetimeSafetyPermissive : 
DiagGroup<"lifetime-safety-permissive",
                                          LifetimeSafetyReturnStackAddr,
                                          LifetimeSafetyDanglingField,
                                          LifetimeSafetyDanglingGlobal,
-                                         LifetimeSafetyUseAfterFree,
-                                         
LifetimeSafetyLifetimeboundViolation]>;
+                                         LifetimeSafetyUseAfterFree]>;
 
 def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
                                     [LifetimeSafetyPermissive,
@@ -673,10 +672,10 @@ Detects misuse of [[clang::noescape]] annotation where 
the parameter escapes (fo
 }
 
 def LifetimeSafetyValidations : DiagGroup<"lifetime-safety-validations",
-                                          [LifetimeSafetyNoescape]> {
+                                          [LifetimeSafetyNoescape, 
LifetimeSafetyLifetimeboundViolation]> {
   code Documentation = [{
 Verify function implementations adhere to the annotated lifetime contracts 
through lifetime safety
-like verifying [[clang::noescape]] and [[clang::lifetimebound]] (upcoming).
+like verifying [[clang::noescape]] and [[clang::lifetimebound]].
   }];
 }
 
diff --git a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp 
b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
new file mode 100644
index 0000000000000..89313b81c15fe
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
@@ -0,0 +1,80 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-all -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct [[gsl::Owner]] MyObj {
+  int id;
+  ~MyObj() {}  // Non-trivial destructor
+};
+
+struct [[gsl::Pointer()]] View {
+  View(const MyObj &); // Borrows from MyObj
+  View();
+  void use() const;
+};
+
+bool cond();
+
+View not_lb(const MyObj &obj);
+
+View lb(const MyObj &obj [[clang::lifetimebound]]);
+
+View return_through_unannotated_passthrough(
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+  return not_lb(obj);
+}
+
+View return_through_lifetimebound_passthrough(
+    const MyObj &obj [[clang::lifetimebound]]) {
+  return lb(obj);
+}
+
+View lb2(const MyObj &obj [[clang::lifetimebound]]) {
+  return lb(obj);
+}
+
+View lose_lb(const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+  return not_lb(obj);
+}
+
+View return_through_alias(const MyObj &obj [[clang::lifetimebound]]) {
+  const MyObj &alias = obj;
+  return alias;
+}
+
+View return_alias_through_unannotated_passthrough(
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+  const MyObj &alias = obj;
+  return not_lb(alias);
+}
+
+View return_through_two_lifetimebound_calls(
+    const MyObj &obj [[clang::lifetimebound]]) {
+  return lb2(obj);
+}
+
+View not_lb_view(View v);
+
+View lb_view(View v [[clang::lifetimebound]]);
+
+View return_through_nested_broken_chain(
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+  return not_lb_view(lb_view(View(obj)));
+}
+
+View return_constructed_view_through_unannotated_forwarder(
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+  return not_lb_view(View(obj));
+}
+
+View return_constructed_view_through_lifetimebound_forwarder(
+    const MyObj &obj [[clang::lifetimebound]]) {
+  return lb_view(View(obj));
+}
+
+View verify_each_annotated_param_independently(
+    const MyObj &a [[clang::lifetimebound]],
+    const MyObj &b [[clang::lifetimebound]], // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
+    const MyObj &c [[clang::lifetimebound]]) { // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
+  return cond() ? a : not_lb(b);
+}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 6eb81995aaa4a..30b450c333fbd 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3269,7 +3269,7 @@ void uaf_via_lifetimebound() {
 namespace GH126600 {
 struct [[gsl::Pointer]] function_ref {
   template <typename Callable>
-  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) 
{} // expected-warning {{parameter is marked as [[clang::lifetimebound]] but 
isn't returned}}
+  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
   void (*ref)();
 };
 
@@ -3279,80 +3279,8 @@ struct [[gsl::Pointer]] function_ref {
 // avoid this warning for non-capturing lambdas.
 void assign_non_capturing_to_function_ref(function_ref &r) {
   r = []() {}; // expected-warning {{object whose reference is captured does 
not live long enough}} \
-               // expected-note {{destroyed here}} function-note {{requested 
here}}
+               // expected-note {{destroyed here}}
   (void)r; // expected-note {{later used here}}
 }
 
 } // namespace GH126600
-
-namespace LifetimeboundReturnVerification {
-
-bool cond();
-
-View drop_lb(const MyObj &obj) { return obj; }
-
-View keep_lb(const MyObj &obj [[clang::lifetimebound]]) {
-  return obj;
-}
-
-View return_through_unannotated_passthrough(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
-  return drop_lb(obj);
-}
-
-View return_through_lifetimebound_passthrough(
-    const MyObj &obj [[clang::lifetimebound]]) {
-  return keep_lb(obj);
-}
-
-View keep_lb2(const MyObj &obj [[clang::lifetimebound]]) {
-  return keep_lb(obj);
-}
-
-View lose_lb(const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
-  return drop_lb(obj);
-}
-
-View return_through_alias(const MyObj &obj [[clang::lifetimebound]]) {
-  const MyObj &alias = obj;
-  return alias;
-}
-
-View return_alias_through_unannotated_passthrough(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
-  const MyObj &alias = obj;
-  return drop_lb(alias);
-}
-
-View return_through_two_lifetimebound_calls(
-    const MyObj &obj [[clang::lifetimebound]]) {
-  return keep_lb2(obj);
-}
-
-View fwd_view(View v) { return v; }
-
-View fwd_view_lb(View v [[clang::lifetimebound]]) { return v; }
-
-View return_through_nested_broken_chain(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
-  return fwd_view(fwd_view_lb(View(obj)));
-}
-
-View return_constructed_view_through_unannotated_forwarder(
-    const MyObj &obj [[clang::lifetimebound]]) { // function-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
-  return fwd_view(View(obj));
-}
-
-View return_constructed_view_through_lifetimebound_forwarder(
-    const MyObj &obj [[clang::lifetimebound]]) {
-  return fwd_view_lb(View(obj));
-}
-
-View verify_each_annotated_param_independently(
-    const MyObj &a [[clang::lifetimebound]],
-    const MyObj &b [[clang::lifetimebound]], // function-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
-    const MyObj &c [[clang::lifetimebound]]) { // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
-  return cond() ? a : drop_lb(b);
-}
-
-} // namespace LifetimeboundReturnVerification

>From 18819006659d9630255630b49c64649b1a5d7f82 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 8 May 2026 16:52:10 +0300
Subject: [PATCH 07/11] upd doc

---
 clang/docs/LifetimeSafety.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/docs/LifetimeSafety.rst b/clang/docs/LifetimeSafety.rst
index c71816dd75a82..a9e3255bcb271 100644
--- a/clang/docs/LifetimeSafety.rst
+++ b/clang/docs/LifetimeSafety.rst
@@ -467,6 +467,7 @@ enables only the high-confidence subset of these checks.
 * ``-Wlifetime-safety-validations``: Enables checks that validate existing 
lifetime annotations.
 
   * ``-Wlifetime-safety-noescape``: Warns when a parameter marked with 
``[[clang::noescape]]`` escapes the function.
+  * ``-Wlifetime-safety-lifetimebound-violation``: Warns when a parameter 
marked with ``[[clang::lifetimebound]]`` is not returned from the function.
 
 Limitations
 ===========

>From c32934de48600cd6734acb681baa01b8567d3897 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 8 May 2026 17:05:38 +0300
Subject: [PATCH 08/11] fixup

---
 clang/lib/Sema/SemaLifetimeSafety.h                    | 5 ++++-
 clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index e900caadf1072..6371b70b13b98 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -35,7 +35,10 @@ inline bool IsLifetimeSafetyDiagnosticEnabled(Sema &S, const 
Decl *D) {
          !Diags.isIgnored(diag::warn_lifetime_safety_invalidation,
                           D->getBeginLoc()) ||
          !Diags.isIgnored(diag::warn_lifetime_safety_noescape_escapes,
-                          D->getBeginLoc());
+                          D->getBeginLoc()) ||
+         !Diags.isIgnored(
+             diag::warn_lifetime_safety_param_lifetimebound_violation,
+             D->getBeginLoc());
 }
 
 class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
diff --git a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp 
b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
index 89313b81c15fe..2783639eb5e42 100644
--- a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-all -verify %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-lifetimebound-violation 
-verify %s
 
 #include "Inputs/lifetime-analysis.h"
 

>From 4767663a52404ac4c2d2c449727d7d9fd8fb7a5b Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 8 May 2026 18:33:02 +0300
Subject: [PATCH 09/11] change wording

---
 clang/include/clang/Basic/DiagnosticGroups.td      |  3 ++-
 clang/include/clang/Basic/DiagnosticSemaKinds.td   |  2 +-
 clang/lib/Sema/SemaLifetimeSafety.h                |  1 +
 .../Sema/warn-lifetime-safety-lifetimebound.cpp    | 14 +++++++-------
 4 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index f4e0d58b7ba1b..69a1a1979c8cd 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -626,7 +626,8 @@ Warning to detect invalidation of references.
 
 def LifetimeSafetyLifetimeboundViolation : 
DiagGroup<"lifetime-safety-lifetimebound-violation"> {
   code Documentation = [{
-Detects parameters marked as [[clang::lifetimebound]] that do not escape 
through the return value.
+Detects parameters marked as [[clang::lifetimebound]] for which we could not 
verify that the return value can be lifetime bound to them.
+This may contain false-positives, e.g. when we fail to track origin 
propagation for the return value.
   }];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ce9e00e4c48eb..9191775447ad1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11004,7 +11004,7 @@ def warn_lifetime_safety_dangling_global_moved
       DefaultIgnore;
 
 def warn_lifetime_safety_param_lifetimebound_violation
-    : Warning<"parameter is marked as [[clang::lifetimebound]] but isn't 
returned">,
+    : Warning<"could not verify that the return value can be lifetime bound to 
'%0'">,
       InGroup<LifetimeSafetyLifetimeboundViolation>,
       DefaultIgnore;
 
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index 6371b70b13b98..96aafb44991e1 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -179,6 +179,7 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
       const ParmVarDecl *ParmWithLifetimebound) override {
     S.Diag(ParmWithLifetimebound->getLocation(),
            diag::warn_lifetime_safety_param_lifetimebound_violation)
+        << ParmWithLifetimebound->getName()
         << ParmWithLifetimebound->getSourceRange();
   }
 
diff --git a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp 
b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
index 2783639eb5e42..8799e554485de 100644
--- a/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-lifetimebound.cpp
@@ -20,7 +20,7 @@ View not_lb(const MyObj &obj);
 View lb(const MyObj &obj [[clang::lifetimebound]]);
 
 View return_through_unannotated_passthrough(
-    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning {{could 
not verify that the return value can be lifetime bound to 'obj'}}
   return not_lb(obj);
 }
 
@@ -33,7 +33,7 @@ View lb2(const MyObj &obj [[clang::lifetimebound]]) {
   return lb(obj);
 }
 
-View lose_lb(const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+View lose_lb(const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{could not verify that the return value can be lifetime bound to 'obj'}}
   return not_lb(obj);
 }
 
@@ -43,7 +43,7 @@ View return_through_alias(const MyObj &obj 
[[clang::lifetimebound]]) {
 }
 
 View return_alias_through_unannotated_passthrough(
-    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning {{could 
not verify that the return value can be lifetime bound to 'obj'}}
   const MyObj &alias = obj;
   return not_lb(alias);
 }
@@ -58,12 +58,12 @@ View not_lb_view(View v);
 View lb_view(View v [[clang::lifetimebound]]);
 
 View return_through_nested_broken_chain(
-    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning {{could 
not verify that the return value can be lifetime bound to 'obj'}}
   return not_lb_view(lb_view(View(obj)));
 }
 
 View return_constructed_view_through_unannotated_forwarder(
-    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning 
{{parameter is marked as [[clang::lifetimebound]] but isn't returned}}
+    const MyObj &obj [[clang::lifetimebound]]) { // expected-warning {{could 
not verify that the return value can be lifetime bound to 'obj'}}
   return not_lb_view(View(obj));
 }
 
@@ -74,7 +74,7 @@ View return_constructed_view_through_lifetimebound_forwarder(
 
 View verify_each_annotated_param_independently(
     const MyObj &a [[clang::lifetimebound]],
-    const MyObj &b [[clang::lifetimebound]], // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
-    const MyObj &c [[clang::lifetimebound]]) { // expected-warning {{parameter 
is marked as [[clang::lifetimebound]] but isn't returned}}
+    const MyObj &b [[clang::lifetimebound]], // expected-warning {{could not 
verify that the return value can be lifetime bound to 'b'}}
+    const MyObj &c [[clang::lifetimebound]]) { // expected-warning {{could not 
verify that the return value can be lifetime bound to 'c'}}
   return cond() ? a : not_lb(b);
 }

>From 98d0d2526ee07c4741d0d392d2d73890947ee61f Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 8 May 2026 18:40:01 +0300
Subject: [PATCH 10/11] newline

---
 clang/include/clang/Basic/DiagnosticGroups.td | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 69a1a1979c8cd..fe21cc0471d6b 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -673,7 +673,8 @@ Detects misuse of [[clang::noescape]] annotation where the 
parameter escapes (fo
 }
 
 def LifetimeSafetyValidations : DiagGroup<"lifetime-safety-validations",
-                                          [LifetimeSafetyNoescape, 
LifetimeSafetyLifetimeboundViolation]> {
+                                          [LifetimeSafetyNoescape,
+                                           
LifetimeSafetyLifetimeboundViolation]> {
   code Documentation = [{
 Verify function implementations adhere to the annotated lifetime contracts 
through lifetime safety
 like verifying [[clang::noescape]] and [[clang::lifetimebound]].

>From 5e35062d768f5943e4b9a313c482e442a2cb9d34 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Fri, 8 May 2026 21:49:53 +0300
Subject: [PATCH 11/11] change wording a bit

---
 clang/docs/LifetimeSafety.rst                 | 2 +-
 clang/include/clang/Basic/DiagnosticGroups.td | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/docs/LifetimeSafety.rst b/clang/docs/LifetimeSafety.rst
index a9e3255bcb271..db166db0637ca 100644
--- a/clang/docs/LifetimeSafety.rst
+++ b/clang/docs/LifetimeSafety.rst
@@ -467,7 +467,7 @@ enables only the high-confidence subset of these checks.
 * ``-Wlifetime-safety-validations``: Enables checks that validate existing 
lifetime annotations.
 
   * ``-Wlifetime-safety-noescape``: Warns when a parameter marked with 
``[[clang::noescape]]`` escapes the function.
-  * ``-Wlifetime-safety-lifetimebound-violation``: Warns when a parameter 
marked with ``[[clang::lifetimebound]]`` is not returned from the function.
+  * ``-Wlifetime-safety-lifetimebound-violation``: Warns when the analysis 
cannot verify that the return value can be lifetime bound to a parameter marked 
with ``[[clang::lifetimebound]]``.
 
 Limitations
 ===========
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index fe21cc0471d6b..03d423db9d21a 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -626,8 +626,8 @@ Warning to detect invalidation of references.
 
 def LifetimeSafetyLifetimeboundViolation : 
DiagGroup<"lifetime-safety-lifetimebound-violation"> {
   code Documentation = [{
-Detects parameters marked as [[clang::lifetimebound]] for which we could not 
verify that the return value can be lifetime bound to them.
-This may contain false-positives, e.g. when we fail to track origin 
propagation for the return value.
+Detects parameters marked as [[clang::lifetimebound]] for which the analysis 
could not verify that the return value can be lifetime bound to the parameter.
+This warning may produce false-positives diagnostics when it cannot fully 
model the code.
   }];
 }
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to