https://github.com/vbvictor updated 
https://github.com/llvm/llvm-project/pull/177260

>From a2cbddef40454b62d4ae6cf34caaa694f14197fe Mon Sep 17 00:00:00 2001
From: Victor Baranov <[email protected]>
Date: Thu, 22 Jan 2026 01:18:49 +0300
Subject: [PATCH 1/7] [LifetimeSafety] Add report on misuse of clang::noescape

---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |   4 +
 clang/include/clang/Basic/DiagnosticGroups.td |   6 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  16 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  11 ++
 .../Sema/warn-lifetime-safety-noescape.cpp    | 160 ++++++++++++++++++
 6 files changed, 202 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Sema/warn-lifetime-safety-noescape.cpp

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 9c91355355233..f0682580c8340 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -60,6 +60,10 @@ class LifetimeSafetyReporter {
   virtual void suggestAnnotation(SuggestionScope Scope,
                                  const ParmVarDecl *ParmToAnnotate,
                                  const Expr *EscapeExpr) {}
+
+  // Reports misuse of [[clang::noescape]] when parameter escapes through 
return
+  virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+                                       const Expr *EscapeExpr) {}
 };
 
 /// The main entry point for the analysis.
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index de1d1e13ea712..fc7f7c58e855d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -553,6 +553,12 @@ def LifetimeSafetySuggestions
     Lifetime annotation suggestions for function parameters that should be 
marked [[clang::lifetimebound]] based on lifetime analysis.
   }];
 }
+def LifetimeSafetyNoescape
+    : DiagGroup<"experimental-lifetime-safety-noescape"> {
+  code Documentation = [{
+    Detects misuse of [[clang::noescape]] annotation where the parameter 
escapes through return.
+  }];
+}
 
 def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">;
 def DllexportExplicitInstantiationDecl : 
DiagGroup<"dllexport-explicit-instantiation-decl">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a2be7ab3791b9..9e066ebf70672 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10834,6 +10834,12 @@ def warn_lifetime_safety_cross_tu_suggestion
 
 def note_lifetime_safety_suggestion_returned_here : Note<"param returned 
here">;
 
+def warn_lifetime_safety_noescape_escapes
+    : Warning<
+          "parameter is marked [[clang::noescape]] but escapes through 
return">,
+      InGroup<LifetimeSafetyNoescape>,
+      DefaultIgnore;
+
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
 // Array comparisons have similar warnings
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index f7383126fac38..461e200f5856a 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -53,6 +53,7 @@ class LifetimeChecker {
 private:
   llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
   llvm::DenseMap<const ParmVarDecl *, const Expr *> AnnotationWarningsMap;
+  llvm::DenseMap<const ParmVarDecl *, const Expr *> NoescapeWarningsMap;
   const LoanPropagationAnalysis &LoanPropagation;
   const LiveOriginsAnalysis &LiveOrigins;
   const FactManager &FactMgr;
@@ -73,6 +74,7 @@ class LifetimeChecker {
           checkAnnotations(OEF);
     issuePendingWarnings();
     suggestAnnotations();
+    reportNoescapeViolations();
     //  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.
@@ -81,7 +83,8 @@ class LifetimeChecker {
   }
 
   /// Checks if an escaping origin holds a placeholder loan, indicating a
-  /// missing [[clang::lifetimebound]] annotation.
+  /// missing [[clang::lifetimebound]] annotation or a violation of
+  /// [[clang::noescape]].
   void checkAnnotations(const OriginEscapesFact *OEF) {
     OriginID EscapedOID = OEF->getEscapedOriginID();
     LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
@@ -89,6 +92,10 @@ class LifetimeChecker {
       const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
       if (const auto *PL = dyn_cast<PlaceholderLoan>(L)) {
         const ParmVarDecl *PVD = PL->getParmVarDecl();
+        if (PVD->hasAttr<NoEscapeAttr>()) {
+          NoescapeWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
+          continue;
+        }
         if (PVD->hasAttr<LifetimeBoundAttr>())
           continue;
         AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
@@ -194,6 +201,13 @@ class LifetimeChecker {
     }
   }
 
+  void reportNoescapeViolations() {
+    if (!Reporter)
+      return;
+    for (const auto &[PVD, EscapeExpr] : NoescapeWarningsMap)
+      Reporter->reportNoescapeViolation(PVD, EscapeExpr);
+  }
+
   void inferAnnotations() {
     for (const auto &[ConstPVD, EscapeExpr] : AnnotationWarningsMap) {
       ParmVarDecl *PVD = const_cast<ParmVarDecl *>(ConstPVD);
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 14d8618f0afdd..49a6fe52c4ef9 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2927,6 +2927,17 @@ class LifetimeSafetyReporterImpl : public 
LifetimeSafetyReporter {
         << EscapeExpr->getSourceRange();
   }
 
+  void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
+                               const Expr *EscapeExpr) override {
+    S.Diag(ParmWithNoescape->getBeginLoc(),
+           diag::warn_lifetime_safety_noescape_escapes)
+        << ParmWithNoescape->getSourceRange();
+
+    S.Diag(EscapeExpr->getBeginLoc(),
+           diag::note_lifetime_safety_suggestion_returned_here)
+        << EscapeExpr->getSourceRange();
+  }
+
 private:
   Sema &S;
 };
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
new file mode 100644
index 0000000000000..813ae54fa9e45
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -0,0 +1,160 @@
+// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s
+
+struct [[gsl::Owner]] MyObj {
+  int id;
+  ~MyObj() {}  // Non-trivial destructor
+};
+
+struct [[gsl::Pointer()]] View {
+  View(const MyObj&); // Borrows from MyObj
+  View();
+  void use() const;
+};
+
+View return_noescape_directly(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+  return in; // expected-note {{returned here}}
+}
+
+View return_one_of_two(
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& b [[clang::noescape]],
+    bool cond) {
+  if (cond)
+    return a; // expected-note {{returned here}}
+  return View();
+}
+
+View return_both(
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    bool cond) {
+  if (cond)
+    return a; // expected-note {{returned here}}
+  return b;   // expected-note {{returned here}}
+}
+
+int* return_noescape_pointer(int* p [[clang::noescape]]) { // expected-warning 
{{parameter is marked [[clang::noescape]] but escapes through return}}
+  return p; // expected-note {{returned here}}
+}
+
+MyObj& return_noescape_reference(MyObj& r [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+  return r; // expected-note {{returned here}}
+}
+
+View return_via_local(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+  View v = in;
+  return v; // expected-note {{returned here}}
+}
+
+void use_locally(const MyObj& in [[clang::noescape]]) {
+  View v = in;
+  v.use();
+}
+
+View return_unrelated(const MyObj& in [[clang::noescape]]) {
+  (void)in;
+  MyObj local;
+  return local;
+}
+
+View return_without_noescape(const MyObj& in) {
+  return in;
+}
+
+View return_with_lifetimebound(const MyObj& in [[clang::lifetimebound]]) {
+  return in;
+}
+
+void pointer_used_locally(MyObj* p [[clang::noescape]]) {
+  p->id = 42;
+}
+
+// Both noescape and lifetimebound - contradictory annotations
+// noescape should take precedence and warn since the parameter does escape
+View both_noescape_and_lifetimebound(
+    const MyObj& in [[clang::noescape]] [[clang::lifetimebound]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+  return in; // expected-note {{returned here}}
+}
+
+View mixed_noescape_lifetimebound(
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& b [[clang::lifetimebound]],
+    bool cond) {
+  if (cond)
+    return a; // expected-note {{returned here}}
+  return b;
+}
+
+View mixed_only_noescape_escapes(
+    const MyObj& a [[clang::noescape]],
+    const MyObj& b [[clang::lifetimebound]]) {
+  (void)a;
+  return b;
+}
+
+View identity_lifetimebound(View v [[clang::lifetimebound]]) { return v; }
+
+View escape_through_lifetimebound_call(
+    const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+  return identity_lifetimebound(in); // expected-note {{returned here}}
+}
+
+View no_annotation_identity(View v) { return v; }
+
+// FIXME: Escaping through a function without lifetimebound is not detected.
+View escape_through_unannotated_call(const MyObj& in [[clang::noescape]]) {
+  return no_annotation_identity(in);  // Not detected - no lifetimebound
+}
+
+View reassign_to_second(
+    const MyObj& a [[clang::noescape]],
+    const MyObj& b [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+  View v = a;
+  v = b;
+  return v; // expected-note {{returned here}}
+}
+
+View multiple_reassign(
+    const MyObj& a [[clang::noescape]],
+    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& c [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    bool cond) {
+  View v = a;
+  if (cond)
+    v = b;
+  else
+    v = c;
+  return v; // expected-note 2 {{returned here}}
+}
+
+struct Container {
+  MyObj data;
+  const MyObj& getRef() const [[clang::lifetimebound]] { return data; }
+};
+
+View access_noescape_field(
+    const Container& c [[clang::noescape]]) { // expected-warning {{parameter 
is marked [[clang::noescape]] but escapes through return}}
+  return c.data; // expected-note {{returned here}}
+}
+
+View access_noescape_through_getter(
+    Container& c [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+  return c.getRef(); // expected-note {{returned here}}
+}
+
+MyObj* return_ptr_from_noescape_ref(
+    MyObj& r [[clang::noescape]]) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes through return}}
+  return &r; // expected-note {{returned here}}
+}
+
+MyObj& return_ref_from_noescape_ptr(
+    MyObj* p [[clang::noescape]]) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes through return}}
+  return *p; // expected-note {{returned here}}
+}
+
+View construct_but_return_other(const MyObj& in [[clang::noescape]]) {
+  View v = in;
+  v.use();
+  MyObj other;
+  return other;
+}

>From 1692f395058973b552e2f40224150baed0f1d19c Mon Sep 17 00:00:00 2001
From: Victor Baranov <[email protected]>
Date: Thu, 22 Jan 2026 01:24:04 +0300
Subject: [PATCH 2/7] reorder tests

---
 .../Sema/warn-lifetime-safety-noescape.cpp    | 61 +++++++++----------
 1 file changed, 30 insertions(+), 31 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index 813ae54fa9e45..a8db83ce63cb5 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -33,6 +33,35 @@ View return_both(
   return b;   // expected-note {{returned here}}
 }
 
+View mixed_noescape_lifetimebound(
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& b [[clang::lifetimebound]],
+    bool cond) {
+  if (cond)
+    return a; // expected-note {{returned here}}
+  return b;
+}
+
+View mixed_only_noescape_escapes(
+    const MyObj& a [[clang::noescape]],
+    const MyObj& b [[clang::lifetimebound]]) {
+  (void)a;
+  return b;
+}
+
+View multiple_reassign(
+    const MyObj& a [[clang::noescape]],
+    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& c [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    bool cond) {
+  View v = a;
+  if (cond)
+    v = b;
+  else
+    v = c;
+  return v; // expected-note 2 {{returned here}}
+}
+
 int* return_noescape_pointer(int* p [[clang::noescape]]) { // expected-warning 
{{parameter is marked [[clang::noescape]] but escapes through return}}
   return p; // expected-note {{returned here}}
 }
@@ -69,29 +98,12 @@ void pointer_used_locally(MyObj* p [[clang::noescape]]) {
   p->id = 42;
 }
 
-// Both noescape and lifetimebound - contradictory annotations
-// noescape should take precedence and warn since the parameter does escape
+// Noescape should take precedence and warn since the parameter does escape
 View both_noescape_and_lifetimebound(
     const MyObj& in [[clang::noescape]] [[clang::lifetimebound]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
   return in; // expected-note {{returned here}}
 }
 
-View mixed_noescape_lifetimebound(
-    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
-    const MyObj& b [[clang::lifetimebound]],
-    bool cond) {
-  if (cond)
-    return a; // expected-note {{returned here}}
-  return b;
-}
-
-View mixed_only_noescape_escapes(
-    const MyObj& a [[clang::noescape]],
-    const MyObj& b [[clang::lifetimebound]]) {
-  (void)a;
-  return b;
-}
-
 View identity_lifetimebound(View v [[clang::lifetimebound]]) { return v; }
 
 View escape_through_lifetimebound_call(
@@ -114,19 +126,6 @@ View reassign_to_second(
   return v; // expected-note {{returned here}}
 }
 
-View multiple_reassign(
-    const MyObj& a [[clang::noescape]],
-    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
-    const MyObj& c [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
-    bool cond) {
-  View v = a;
-  if (cond)
-    v = b;
-  else
-    v = c;
-  return v; // expected-note 2 {{returned here}}
-}
-
 struct Container {
   MyObj data;
   const MyObj& getRef() const [[clang::lifetimebound]] { return data; }

>From 8fd4772b596a9be111d06abba943336c9b5f8cd8 Mon Sep 17 00:00:00 2001
From: Victor Baranov <[email protected]>
Date: Sat, 24 Jan 2026 14:11:36 +0300
Subject: [PATCH 3/7] fix review

---
 clang/include/clang/Basic/DiagnosticGroups.td |  2 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 +-
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  2 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  6 ++-
 .../Sema/warn-lifetime-safety-noescape.cpp    | 49 +++++++++++--------
 5 files changed, 36 insertions(+), 26 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index fc7f7c58e855d..f22263fc9f70e 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -556,7 +556,7 @@ def LifetimeSafetySuggestions
 def LifetimeSafetyNoescape
     : DiagGroup<"experimental-lifetime-safety-noescape"> {
   code Documentation = [{
-    Detects misuse of [[clang::noescape]] annotation where the parameter 
escapes through return.
+    Detects misuse of [[clang::noescape]] annotation where the parameter 
escapes.
   }];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9e066ebf70672..c5420798960c1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10835,8 +10835,7 @@ def warn_lifetime_safety_cross_tu_suggestion
 def note_lifetime_safety_suggestion_returned_here : Note<"param returned 
here">;
 
 def warn_lifetime_safety_noescape_escapes
-    : Warning<
-          "parameter is marked [[clang::noescape]] but escapes through 
return">,
+    : Warning<"parameter is marked [[clang::noescape]] but escapes">,
       InGroup<LifetimeSafetyNoescape>,
       DefaultIgnore;
 
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 461e200f5856a..5103a8be482c6 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -204,7 +204,7 @@ class LifetimeChecker {
   void reportNoescapeViolations() {
     if (!Reporter)
       return;
-    for (const auto &[PVD, EscapeExpr] : NoescapeWarningsMap)
+    for (auto [PVD, EscapeExpr] : NoescapeWarningsMap)
       Reporter->reportNoescapeViolation(PVD, EscapeExpr);
   }
 
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 49a6fe52c4ef9..bafbe7e79306b 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2929,10 +2929,12 @@ class LifetimeSafetyReporterImpl : public 
LifetimeSafetyReporter {
 
   void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                const Expr *EscapeExpr) override {
+    const auto *Attr = ParmWithNoescape->getAttr<NoEscapeAttr>();
+
     S.Diag(ParmWithNoescape->getBeginLoc(),
            diag::warn_lifetime_safety_noescape_escapes)
-        << ParmWithNoescape->getSourceRange();
-
+        << ParmWithNoescape->getSourceRange()
+        << FixItHint::CreateRemoval(Attr->getRange());
     S.Diag(EscapeExpr->getBeginLoc(),
            diag::note_lifetime_safety_suggestion_returned_here)
         << EscapeExpr->getSourceRange();
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index a8db83ce63cb5..d8e1b94dc87db 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -1,4 +1,6 @@
-// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s
+// RUN: cp %s %t
+// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -fixit -verify %t
+// RUN: %clang_cc1 -x c++ -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -Werror %t
 
 struct [[gsl::Owner]] MyObj {
   int id;
@@ -11,12 +13,12 @@ struct [[gsl::Pointer()]] View {
   void use() const;
 };
 
-View return_noescape_directly(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+View return_noescape_directly(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
   return in; // expected-note {{returned here}}
 }
 
 View return_one_of_two(
-    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
     const MyObj& b [[clang::noescape]],
     bool cond) {
   if (cond)
@@ -25,8 +27,8 @@ View return_one_of_two(
 }
 
 View return_both(
-    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
-    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
+    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
     bool cond) {
   if (cond)
     return a; // expected-note {{returned here}}
@@ -34,7 +36,7 @@ View return_both(
 }
 
 View mixed_noescape_lifetimebound(
-    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
     const MyObj& b [[clang::lifetimebound]],
     bool cond) {
   if (cond)
@@ -51,8 +53,8 @@ View mixed_only_noescape_escapes(
 
 View multiple_reassign(
     const MyObj& a [[clang::noescape]],
-    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
-    const MyObj& c [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
+    const MyObj& c [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
     bool cond) {
   View v = a;
   if (cond)
@@ -62,15 +64,15 @@ View multiple_reassign(
   return v; // expected-note 2 {{returned here}}
 }
 
-int* return_noescape_pointer(int* p [[clang::noescape]]) { // expected-warning 
{{parameter is marked [[clang::noescape]] but escapes through return}}
+int* return_noescape_pointer(int* p [[clang::noescape]]) { // expected-warning 
{{parameter is marked [[clang::noescape]] but escapes}}
   return p; // expected-note {{returned here}}
 }
 
-MyObj& return_noescape_reference(MyObj& r [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+MyObj& return_noescape_reference(MyObj& r [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
   return r; // expected-note {{returned here}}
 }
 
-View return_via_local(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+View return_via_local(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
   View v = in;
   return v; // expected-note {{returned here}}
 }
@@ -98,16 +100,16 @@ void pointer_used_locally(MyObj* p [[clang::noescape]]) {
   p->id = 42;
 }
 
-// Noescape should take precedence and warn since the parameter does escape
+// Noescape should take precedence and warn since the parameter does escape.
 View both_noescape_and_lifetimebound(
-    const MyObj& in [[clang::noescape]] [[clang::lifetimebound]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes through 
return}}
+    const MyObj& in [[clang::noescape]] [[clang::lifetimebound]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
   return in; // expected-note {{returned here}}
 }
 
 View identity_lifetimebound(View v [[clang::lifetimebound]]) { return v; }
 
 View escape_through_lifetimebound_call(
-    const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& in [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
   return identity_lifetimebound(in); // expected-note {{returned here}}
 }
 
@@ -115,12 +117,19 @@ View no_annotation_identity(View v) { return v; }
 
 // FIXME: Escaping through a function without lifetimebound is not detected.
 View escape_through_unannotated_call(const MyObj& in [[clang::noescape]]) {
-  return no_annotation_identity(in);  // Not detected - no lifetimebound
+  return no_annotation_identity(in);
+}
+
+View view;
+
+// FIXME: Escaping through a global variable is not detected.
+void escape_through_global_var(const MyObj& in [[clang::noescape]]) {
+  view = in;
 }
 
 View reassign_to_second(
     const MyObj& a [[clang::noescape]],
-    const MyObj& b [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    const MyObj& b [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
   View v = a;
   v = b;
   return v; // expected-note {{returned here}}
@@ -132,22 +141,22 @@ struct Container {
 };
 
 View access_noescape_field(
-    const Container& c [[clang::noescape]]) { // expected-warning {{parameter 
is marked [[clang::noescape]] but escapes through return}}
+    const Container& c [[clang::noescape]]) { // expected-warning {{parameter 
is marked [[clang::noescape]] but escapes}}
   return c.data; // expected-note {{returned here}}
 }
 
 View access_noescape_through_getter(
-    Container& c [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes through return}}
+    Container& c [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
   return c.getRef(); // expected-note {{returned here}}
 }
 
 MyObj* return_ptr_from_noescape_ref(
-    MyObj& r [[clang::noescape]]) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes through return}}
+    MyObj& r [[clang::noescape]]) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes}}
   return &r; // expected-note {{returned here}}
 }
 
 MyObj& return_ref_from_noescape_ptr(
-    MyObj* p [[clang::noescape]]) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes through return}}
+    MyObj* p [[clang::noescape]]) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes}}
   return *p; // expected-note {{returned here}}
 }
 

>From a269750660f7c1acf17907c53e6e32d8dec5e957 Mon Sep 17 00:00:00 2001
From: Victor Baranov <[email protected]>
Date: Sat, 24 Jan 2026 14:16:05 +0300
Subject: [PATCH 4/7] back diag group

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

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index f22263fc9f70e..fc7f7c58e855d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -556,7 +556,7 @@ def LifetimeSafetySuggestions
 def LifetimeSafetyNoescape
     : DiagGroup<"experimental-lifetime-safety-noescape"> {
   code Documentation = [{
-    Detects misuse of [[clang::noescape]] annotation where the parameter 
escapes.
+    Detects misuse of [[clang::noescape]] annotation where the parameter 
escapes through return.
   }];
 }
 

>From 97f1d642a2d140e1a864d6bd4eb9e447c65b30a5 Mon Sep 17 00:00:00 2001
From: Victor Baranov <[email protected]>
Date: Sat, 24 Jan 2026 16:20:17 +0300
Subject: [PATCH 5/7] fix failing test

---
 clang/test/Sema/warn-lifetime-safety-noescape.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index d8e1b94dc87db..8e0a27536234b 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -1,5 +1,6 @@
+// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s
 // RUN: cp %s %t
-// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -fixit -verify %t
+// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -fixit %t
 // RUN: %clang_cc1 -x c++ -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -Werror %t
 
 struct [[gsl::Owner]] MyObj {

>From 42050a05aeafbbc511a6ba3f64839ff8d181d9a2 Mon Sep 17 00:00:00 2001
From: Victor Baranov <[email protected]>
Date: Sat, 24 Jan 2026 17:59:42 +0300
Subject: [PATCH 6/7] handle [[  ]] correctly

---
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 31 ++++++++++++++++++-
 .../Sema/warn-lifetime-safety-noescape.cpp    |  4 +++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index bafbe7e79306b..b1646b2f8299c 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2930,11 +2930,40 @@ class LifetimeSafetyReporterImpl : public 
LifetimeSafetyReporter {
   void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                const Expr *EscapeExpr) override {
     const auto *Attr = ParmWithNoescape->getAttr<NoEscapeAttr>();
+    SourceRange RemovalRange = Attr->getRange();
+
+    // For [[clang::noescape]], Attr->getRange() only covers the inner
+    // 'clang::noescape' part. Extend to include the '[[' and ']]' brackets.
+    if (Attr->isStandardAttributeSyntax()) {
+      const SourceManager &SM = S.getSourceManager();
+      const LangOptions &LO = S.getLangOpts();
+
+      auto SecondOpen = Lexer::findPreviousToken(
+          Attr->getRange().getBegin(), SM, LO, /*IncludeComments=*/false);
+      auto FirstOpen =
+          SecondOpen && SecondOpen->is(tok::l_square)
+              ? Lexer::findPreviousToken(SecondOpen->getLocation(), SM, LO,
+                                         /*IncludeComments=*/false)
+              : std::nullopt;
+
+      auto FirstClose = Lexer::findNextToken(Attr->getRange().getEnd(), SM, LO,
+                                             /*IncludeComments=*/false);
+      auto SecondClose =
+          FirstClose && FirstClose->is(tok::r_square)
+              ? Lexer::findNextToken(FirstClose->getLocation(), SM, LO,
+                                     /*IncludeComments=*/false)
+              : std::nullopt;
+
+      if ((FirstOpen && FirstOpen->is(tok::l_square)) &&
+          (SecondClose && SecondClose->is(tok::r_square)))
+        RemovalRange = {FirstOpen->getLocation(), SecondClose->getLocation()};
+    }
 
     S.Diag(ParmWithNoescape->getBeginLoc(),
            diag::warn_lifetime_safety_noescape_escapes)
         << ParmWithNoescape->getSourceRange()
-        << FixItHint::CreateRemoval(Attr->getRange());
+        << FixItHint::CreateRemoval(RemovalRange);
+
     S.Diag(EscapeExpr->getBeginLoc(),
            diag::note_lifetime_safety_suggestion_returned_here)
         << EscapeExpr->getSourceRange();
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index 8e0a27536234b..300c616de1feb 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -167,3 +167,7 @@ View construct_but_return_other(const MyObj& in 
[[clang::noescape]]) {
   MyObj other;
   return other;
 }
+
+int* return_spaced_brackets(int* p [ [clang::noescape] /*some comment*/ ]) { 
// expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+  return p; // expected-note {{returned here}}
+}

>From f093fc8267cfbd3a5af48fcb5949da312cad6fad Mon Sep 17 00:00:00 2001
From: Baranov Victor <[email protected]>
Date: Sat, 24 Jan 2026 19:19:08 +0300
Subject: [PATCH 7/7] Apply suggestion from @vbvictor

---
 clang/test/Sema/warn-lifetime-safety-noescape.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index 300c616de1feb..cf20cbc4ec849 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s
+// RUN: %clang_cc1 -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s
 // RUN: cp %s %t
 // RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -fixit %t
 // RUN: %clang_cc1 -x c++ -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety-noescape -Wno-dangling -Werror %t

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

Reply via email to