https://github.com/AbhinavPradeep updated 
https://github.com/llvm/llvm-project/pull/186126

>From caad4ff4e30b40f84e1baf6fa847f0663000bb61 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Fri, 13 Mar 2026 00:21:23 +1000
Subject: [PATCH 1/7] Added new CallEscapeFact.

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 37 +++++++++++++++++++
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |  7 ++++
 2 files changed, 44 insertions(+)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 94db2a7f311ae..ae981ccf8a2d1 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -15,11 +15,13 @@
 #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H
 
 #include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
 #include "clang/Analysis/CFG.h"
+#include "llvm/ADT/PointerUnion.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Debug.h"
@@ -171,6 +173,7 @@ class OriginEscapesFact : public Fact {
     Return, /// Escapes via return statement.
     Field,  /// Escapes via assignment to a field.
     Global, /// Escapes via assignment to global storage.
+    Call,   /// Escapes as argument to a function call.
   } EscKind;
 
   static bool classof(const Fact *F) {
@@ -240,6 +243,40 @@ class GlobalEscapeFact : public OriginEscapesFact {
             const LoanPropagationAnalysis *LPA = nullptr) const override;
 };
 
+/// Represents escape of an origin through a function call.
+/// Example:
+/// void f(int *i);
+/// void g(int *j[[clang::noescape]]) {f(j)};
+/// This fact enables us to catch that the noescape parameter j escapes through
+/// the call to function f
+class CallEscapeFact : public OriginEscapesFact {
+  // Currently the analysis handles the following call-like expressions:
+  // - VisitCXXOperatorCallExpr to handle CXXOperatorCallExpr, a sub-class of
+  // CallExpr.
+  // - VisitCXXMemberCallExpr to handle CXXMemberCallExpr, a sub-class of
+  // CallExpr.
+  // - VisitCXXConstructExpr and handleGSLPointerConstruction deal with
+  // CXXConstructExpr. Whilst call like, it is not a sub-class of CallExpr.
+  // Therefore, this type is taken to be the union of CallExpr * and
+  // CXXConstructExpr *:
+  using CallLikeExprPtr = llvm::PointerUnion<CallExpr *, CXXConstructExpr *>;
+  const CallLikeExprPtr Call;
+  const unsigned ArgumentIndex;
+
+public:
+  CallEscapeFact(OriginID OID, const CallLikeExprPtr Call, const unsigned 
Index)
+      : OriginEscapesFact(OID, EscapeKind::Call), Call(Call),
+        ArgumentIndex(Index) {}
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::OriginEscapes &&
+           static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
+               EscapeKind::Call;
+  }
+  const CallLikeExprPtr getCall() const { return Call; };
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override;
+};
+
 class UseFact : public Fact {
   const Expr *UseExpr;
   const OriginList *OList;
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index ec2d42e10206a..53bf38bb0b4dc 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -98,6 +98,13 @@ void GlobalEscapeFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
   OS << ", via Global)\n";
 }
 
+void CallEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                          const OriginManager &OM) const {
+  OS << "CallEscapes (";
+  OM.dump(getEscapedOriginID(), OS);
+  OS << ", via Call)\n";
+}
+
 void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
                    const OriginManager &OM,
                    const LoanPropagationAnalysis *) const {

>From 7cd724acb1a9b1cdeb09d44163fead5c550d298e Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Sat, 21 Mar 2026 22:52:08 +1000
Subject: [PATCH 2/7] Fixed up CallEscapeFact and ensured that it does not
 participate in liveness analysis. Added no-escape checking which uses a
 placeholder error message.

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 22 ++++------
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  4 ++
 .../LifetimeSafety/FactsGenerator.cpp         | 40 +++++++++++++++++--
 .../Analysis/LifetimeSafety/LiveOrigins.cpp   |  6 +++
 .../LifetimeSafety/noescape-violation.cpp     |  9 +++--
 5 files changed, 60 insertions(+), 21 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index ae981ccf8a2d1..fb6763b227edc 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -250,29 +250,23 @@ class GlobalEscapeFact : public OriginEscapesFact {
 /// This fact enables us to catch that the noescape parameter j escapes through
 /// the call to function f
 class CallEscapeFact : public OriginEscapesFact {
-  // Currently the analysis handles the following call-like expressions:
-  // - VisitCXXOperatorCallExpr to handle CXXOperatorCallExpr, a sub-class of
-  // CallExpr.
-  // - VisitCXXMemberCallExpr to handle CXXMemberCallExpr, a sub-class of
-  // CallExpr.
-  // - VisitCXXConstructExpr and handleGSLPointerConstruction deal with
-  // CXXConstructExpr. Whilst call like, it is not a sub-class of CallExpr.
-  // Therefore, this type is taken to be the union of CallExpr * and
-  // CXXConstructExpr *:
-  using CallLikeExprPtr = llvm::PointerUnion<CallExpr *, CXXConstructExpr *>;
-  const CallLikeExprPtr Call;
+  const Expr *Call;
+  const Expr *Argument;
   const unsigned ArgumentIndex;
 
 public:
-  CallEscapeFact(OriginID OID, const CallLikeExprPtr Call, const unsigned 
Index)
+  CallEscapeFact(OriginID OID, const Expr *Call, const unsigned Index,
+                 const Expr *Argument)
       : OriginEscapesFact(OID, EscapeKind::Call), Call(Call),
-        ArgumentIndex(Index) {}
+        Argument(Argument), ArgumentIndex(Index) {}
   static bool classof(const Fact *F) {
     return F->getKind() == Kind::OriginEscapes &&
            static_cast<const OriginEscapesFact *>(F)->getEscapeKind() ==
                EscapeKind::Call;
   }
-  const CallLikeExprPtr getCall() const { return Call; };
+  const Expr *getCall() const { return Call; };
+  unsigned getArgumentIndex() const { return ArgumentIndex; };
+  const Expr *getArgument() const { return Argument; };
   void dump(llvm::raw_ostream &OS, const LoanManager &,
             const OriginManager &OM) const override;
 };
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 53e5077131147..46b6a2b5c721e 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -131,6 +131,10 @@ class LifetimeChecker {
           NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
         if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
           NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal());
+        if (auto *CallEsc = dyn_cast<CallEscapeFact>(OEF))
+          // Currently this triggers the wrong reporting. Will fix with next
+          // commit!
+          NoescapeWarningsMap.try_emplace(PVD, CallEsc->getArgument());
         return;
       }
       // Skip annotation suggestion for moved loans, as ownership transfer
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index dd2bffe22f4f5..24cd5f1c094d1 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -14,6 +14,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/OperationKinds.h"
+#include "clang/AST/Stmt.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
@@ -21,6 +22,7 @@
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "clang/Analysis/CFG.h"
 #include "clang/Basic/OperatorKinds.h"
+#include "clang/Basic/SourceManager.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Casting.h"
@@ -1077,13 +1079,45 @@ void FactsGenerator::handleFunctionCall(const Expr 
*Call,
                                         ArrayRef<const Expr *> Args,
                                         bool IsGslConstruction) {
   OriginList *CallList = getOriginsList(*Call);
+  SourceManager &SM = AC.getASTContext().getSourceManager();
+  // To avoid over-reporting, we assume the following are noescape:
+  // - All parameters to functions declared in the system headers
+  // - The implicit `this` parameter for member functions
+  auto IsArgNoEscape = [FD, &SM](unsigned I) -> bool {
+    const ParmVarDecl *PVD = nullptr;
+    if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
+        Method && Method->isInstance()) {
+      // There is currently no way to declare 'this' is noescape for member
+      // functions We therefore return true as the user cannot do anything via
+      // annotation, so we make the conservative approximation
+      if (I == 0) {
+        return true;
+      }
+      if ((I - 1) < Method->getNumParams()) {
+        PVD = Method->getParamDecl(I - 1);
+      }
+    } else if (I < FD->getNumParams()) {
+      PVD = FD->getParamDecl(I);
+    }
+    if (PVD && !SM.isInSystemHeader(PVD->getLocation()))
+      return PVD->hasAttr<clang::NoEscapeAttr>();
+    return true;
+  };
+  // All arguments to a function are a use of the corresponding expressions.
+  for (unsigned I = 0; I < Args.size(); ++I) {
+    handleUse(Args[I]);
+    OriginList *ArgList = getOriginsList(*Args[I]);
+    if (!IsArgNoEscape(I)) {
+      for (OriginList *L = ArgList; L; L = L->peelOuterOrigin()) {
+        EscapesInCurrentBlock.push_back(FactMgr.createFact<CallEscapeFact>(
+            L->getOuterOriginID(), Call, I, Args[I]));
+      }
+    }
+  }
   // Ignore functions returning values with no origin.
   FD = getDeclWithMergedLifetimeBoundAttrs(FD);
   if (!FD)
     return;
-  // All arguments to a function are a use of the corresponding expressions.
-  for (const Expr *Arg : Args)
-    handleUse(Arg);
   handleInvalidatingCall(Call, FD, Args);
   handleDestructiveCall(Call, FD, Args);
   handleMovedArgsInCall(FD, Args);
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp 
b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index 69b903c813555..b28c5f0f91f90 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -9,6 +9,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
 #include "Dataflow.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/ErrorHandling.h"
 
 namespace clang::lifetimes::internal {
@@ -64,6 +65,8 @@ static SourceLocation GetFactLoc(CausingFactType F) {
       return FieldEsc->getFieldDecl()->getLocation();
     if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
       return GlobalEsc->getGlobal()->getLocation();
+    if (auto *CallEsc = dyn_cast<CallEscapeFact>(OEF))
+      return CallEsc->getArgument()->getExprLoc();
   }
   llvm_unreachable("unhandled causing fact in PointerUnion");
 }
@@ -148,6 +151,9 @@ class AnalysisImpl
   /// An escaping origin (e.g., via return) makes the origin live with definite
   /// confidence, as it dominates this program point.
   Lattice transfer(Lattice In, const OriginEscapesFact &OEF) {
+    // CallEscapeFact should not affect liveness
+    if (isa<CallEscapeFact>(&OEF))
+      return In;
     OriginID OID = OEF.getEscapedOriginID();
     return Lattice(Factory.add(In.LiveOrigins, OID,
                                LivenessInfo(&OEF, LivenessKind::Must)));
diff --git a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp 
b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
index 048c500239b4f..43022e7d60195 100644
--- a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
+++ b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
@@ -8,9 +8,10 @@ struct [[gsl::Owner]] MyObj {
 };
 
 struct [[gsl::Pointer()]] View {
-  View(const MyObj&); // Borrows from MyObj
+  View(const MyObj& obj [[clang::noescape]]); // Borrows from MyObj
   View();
   void use() const;
+  void let_parameter_escape(const MyObj& obj) const;
 };
 
 View return_noescape_directly(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
@@ -150,9 +151,9 @@ struct ObjConsumer {
   View member_view; // expected-note {{escapes to this field}}
 };
 
-// FIXME: Escaping through another param is not detected.
-void escape_through_param(const MyObj& in, std::vector<View> &v) {
-  v.push_back(in);
+void escape_through_param(const MyObj& in [[clang::noescape]], 
std::vector<View> &v) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes}}
+  // Has wrong reporting by virtue of how the reportNoescapeViolations is 
written. Will fix in the next commit!
+  v.push_back(in); // expected-note {{returned here}}
 }
 
 View reassign_to_second(

>From 96dd5242ad5d1292ac499114dcdbbc60eac18d19 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Mon, 8 Jun 2026 17:55:20 +1000
Subject: [PATCH 3/7] Rebased and fixed up reporting.

---
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |  5 +++
 .../clang/Basic/DiagnosticSemaKinds.td        |  1 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 32 ++++++++-----------
 clang/lib/Sema/SemaLifetimeSafety.h           | 11 +++++++
 .../LifetimeSafety/noescape-violation.cpp     |  7 ++--
 5 files changed, 33 insertions(+), 23 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index a51ef2f7cc0ba..1dad873cf4faf 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -124,6 +124,11 @@ class LifetimeSafetySemaHelper {
   // assignment to a global variable
   virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
                                        const VarDecl *EscapeGlobal) {}
+  // Reports misuse of [[clang::noescape]] when parameter escapes through
+  // a function call.
+  virtual void
+  reportNoescapeViolationThroughCall(const ParmVarDecl *ParmWithNoescape,
+                                     const Expr *EscapeCall) {}
 
   // Reports misuse of [[clang::lifetimebound]] when parameter doesn't escape
   // through return.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 89e2f956971b3..9c731c1ef3b78 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11102,6 +11102,7 @@ def note_lifetime_safety_escapes_to_global_here: 
Note<"escapes to this global st
 def note_lifetime_safety_escapes_to_static_storage_here: Note<"escapes to this 
static storage">;
 def note_lifetime_safety_lifetimebound_here: Note<"'lifetimebound' attribute 
appears here on the definition">;
 def note_lifetime_safety_aliases_storage : Note<"%0 aliases the storage of 
%1">;
+def note_lifetime_safety_escapes_through_call_here: Note<"escapes through this 
call">;
 
 def warn_lifetime_safety_intra_tu_param_suggestion
     : Warning<"parameter in intra-TU function should be marked "
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 46b6a2b5c721e..af71e7c531007 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -59,7 +59,8 @@ class LifetimeChecker {
 private:
   llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
   llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap;
-  llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
+  llvm::DenseMap<const ParmVarDecl *, const OriginEscapesFact *>
+      NoescapeWarningsMap;
   llvm::DenseSet<const Decl *> VerifiedLiftimeboundEscapes;
   const LoanPropagationAnalysis &LoanPropagation;
   const MovedLoansAnalysis &MovedLoans;
@@ -125,16 +126,7 @@ class LifetimeChecker {
     auto CheckParam = [&](const ParmVarDecl *PVD, bool IsMoved) {
       // NoEscape param should not escape.
       if (PVD->hasAttr<NoEscapeAttr>()) {
-        if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
-          NoescapeWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
-        if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
-          NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
-        if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
-          NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal());
-        if (auto *CallEsc = dyn_cast<CallEscapeFact>(OEF))
-          // Currently this triggers the wrong reporting. Will fix with next
-          // commit!
-          NoescapeWarningsMap.try_emplace(PVD, CallEsc->getArgument());
+        NoescapeWarningsMap.try_emplace(PVD, OEF);
         return;
       }
       // Skip annotation suggestion for moved loans, as ownership transfer
@@ -425,15 +417,17 @@ class LifetimeChecker {
   }
 
   void reportNoescapeViolations() {
-    for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
-      if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
-        SemaHelper->reportNoescapeViolation(PVD, E);
-      else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
-        SemaHelper->reportNoescapeViolation(PVD, FD);
-      else if (const auto *G = EscapeTarget.dyn_cast<const VarDecl *>())
-        SemaHelper->reportNoescapeViolation(PVD, G);
+    for (auto [PVD, OEF] : NoescapeWarningsMap) {
+      if (const auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
+        SemaHelper->reportNoescapeViolation(PVD, ReturnEsc->getReturnExpr());
+      else if (const auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
+        SemaHelper->reportNoescapeViolation(PVD, FieldEsc->getFieldDecl());
+      else if (const auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
+        SemaHelper->reportNoescapeViolation(PVD, GlobalEsc->getGlobal());
+      else if (const auto *CallEsc = dyn_cast<CallEscapeFact>(OEF))
+        SemaHelper->reportNoescapeViolationThroughCall(PVD, 
CallEsc->getCall());
       else
-        llvm_unreachable("Unhandled EscapingTarget type");
+        llvm_unreachable("Unhandled escape fact kind");
     }
   }
 
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index 1d9f94be7e22d..e1c0e67dd1ccb 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -454,6 +454,17 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
           << EscapeGlobal->getEndLoc();
   }
 
+  void reportNoescapeViolationThroughCall(const ParmVarDecl *ParmWithNoescape,
+                                          const Expr *EscapeCall) override {
+    S.Diag(ParmWithNoescape->getBeginLoc(),
+           diag::warn_lifetime_safety_noescape_escapes)
+        << ParmWithNoescape->getSourceRange();
+
+    S.Diag(EscapeCall->getBeginLoc(),
+           diag::note_lifetime_safety_escapes_through_call_here)
+        << EscapeCall->getSourceRange();
+  }
+
   void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
     S.addLifetimeBoundToImplicitThis(const_cast<CXXMethodDecl *>(MD));
   }
diff --git a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp 
b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
index 43022e7d60195..7ae0dcbcfcdb3 100644
--- a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
+++ b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
@@ -105,13 +105,13 @@ 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}}
-  return identity_lifetimebound(in); // expected-note {{returned here}}
+  return identity_lifetimebound(in); // expected-note {{escapes through this 
call}}
 }
 
 View no_annotation_identity(View v) { return v; }
 
 View escape_through_unannotated_call(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
-  return no_annotation_identity(in); // expected-note {{returned here}}
+  return no_annotation_identity(in); // expected-note {{escapes through this 
call}}
 }
 
 View global_view; // expected-note {{escapes to this global storage}}
@@ -152,8 +152,7 @@ struct ObjConsumer {
 };
 
 void escape_through_param(const MyObj& in [[clang::noescape]], 
std::vector<View> &v) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes}}
-  // Has wrong reporting by virtue of how the reportNoescapeViolations is 
written. Will fix in the next commit!
-  v.push_back(in); // expected-note {{returned here}}
+  v.push_back(in); // expected-note {{escapes through this call}}
 }
 
 View reassign_to_second(

>From 4e70b2f7a5ac99da7809e6f6c9aa82902042a8ce Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Mon, 20 Jul 2026 15:39:56 +1000
Subject: [PATCH 4/7] Added guard against indirect function calls

---
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 24cd5f1c094d1..d52da19de18bf 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -1079,6 +1079,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
                                         ArrayRef<const Expr *> Args,
                                         bool IsGslConstruction) {
   OriginList *CallList = getOriginsList(*Call);
+  FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+  if (!FD)
+    return;
   SourceManager &SM = AC.getASTContext().getSourceManager();
   // To avoid over-reporting, we assume the following are noescape:
   // - All parameters to functions declared in the system headers
@@ -1114,10 +1117,6 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
       }
     }
   }
-  // Ignore functions returning values with no origin.
-  FD = getDeclWithMergedLifetimeBoundAttrs(FD);
-  if (!FD)
-    return;
   handleInvalidatingCall(Call, FD, Args);
   handleDestructiveCall(Call, FD, Args);
   handleMovedArgsInCall(FD, Args);

>From 8fe828b97e072b2ec7324fe235957c434f294328 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Tue, 21 Jul 2026 14:53:58 +1000
Subject: [PATCH 5/7] Added strict noescape warning group and moved function
 call escape to it.

---
 clang/include/clang/Basic/DiagnosticGroups.td | 11 +++++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  5 ++++
 .../LifetimeSafety/FactsGenerator.cpp         |  9 +++++-
 clang/lib/Sema/SemaLifetimeSafety.h           |  2 +-
 .../LifetimeSafety/noescape-violation.cpp     | 17 -----------
 .../warn-lifetime-safety-noescape-strict.cpp  | 30 +++++++++++++++++++
 6 files changed, 55 insertions(+), 19 deletions(-)
 create mode 100644 clang/test/Sema/warn-lifetime-safety-noescape-strict.cpp

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 79583534b9bbd..db32d607a6479 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -707,6 +707,17 @@ def LifetimeSafetyAnnotationPlacement
     : DiagGroup<"lifetime-safety-annotation-placement",
                 [LifetimeSafetyInapplicableLifetimebound,
                  LifetimeSafetyMisplacedLifetimebound]> {
+def LifetimeSafetyNoescapeStrict
+    : DiagGroup<"lifetime-safety-noescape-strict", [LifetimeSafetyNoescape]> {
+  code Documentation = [{
+Enables stricter detection of [[clang::noescape]] annotation misuse (for 
example, through function calls).
+  }];
+}
+
+def LifetimeSafetyValidations : DiagGroup<"lifetime-safety-validations",
+                                          [LifetimeSafetyNoescape,
+                                           
LifetimeSafetyLifetimeboundViolation,
+                                           
LifetimeSafetyMisplacedLifetimebound]> {
   code Documentation = [{
 Validates that lifetime annotations are placed on appropriate types and in 
appropriate locations.
   }];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9c731c1ef3b78..e68a9077c23c6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11147,6 +11147,11 @@ def warn_lifetime_safety_noescape_escapes
       InGroup<LifetimeSafetyNoescape>,
       DefaultIgnore;
 
+def warn_lifetime_safety_noescape_escapes_through_call
+    : Warning<"parameter is marked [[clang::noescape]] but escapes">,
+      InGroup<LifetimeSafetyNoescapeStrict>,
+      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/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d52da19de18bf..d6757f18e651a 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -21,6 +21,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "clang/Analysis/CFG.h"
+#include "clang/Basic/DiagnosticSema.h"
 #include "clang/Basic/OperatorKinds.h"
 #include "clang/Basic/SourceManager.h"
 #include "llvm/ADT/ArrayRef.h"
@@ -1082,6 +1083,12 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
   FD = getDeclWithMergedLifetimeBoundAttrs(FD);
   if (!FD)
     return;
+  // Check to see if we need to report for escape through function call.
+  // Used to gate generation of CallEscapeFact.
+  const bool EnableNoescapeCallEscapes =
+      !AC.getASTContext().getDiagnostics().isIgnored(
+          diag::warn_lifetime_safety_noescape_escapes_through_call,
+          Call->getBeginLoc());
   SourceManager &SM = AC.getASTContext().getSourceManager();
   // To avoid over-reporting, we assume the following are noescape:
   // - All parameters to functions declared in the system headers
@@ -1110,7 +1117,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
   for (unsigned I = 0; I < Args.size(); ++I) {
     handleUse(Args[I]);
     OriginList *ArgList = getOriginsList(*Args[I]);
-    if (!IsArgNoEscape(I)) {
+    if (EnableNoescapeCallEscapes && !IsArgNoEscape(I)) {
       for (OriginList *L = ArgList; L; L = L->peelOuterOrigin()) {
         EscapesInCurrentBlock.push_back(FactMgr.createFact<CallEscapeFact>(
             L->getOuterOriginID(), Call, I, Args[I]));
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h 
b/clang/lib/Sema/SemaLifetimeSafety.h
index e1c0e67dd1ccb..69b7fadfa2c4c 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -457,7 +457,7 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
   void reportNoescapeViolationThroughCall(const ParmVarDecl *ParmWithNoescape,
                                           const Expr *EscapeCall) override {
     S.Diag(ParmWithNoescape->getBeginLoc(),
-           diag::warn_lifetime_safety_noescape_escapes)
+           diag::warn_lifetime_safety_noescape_escapes_through_call)
         << ParmWithNoescape->getSourceRange();
 
     S.Diag(EscapeCall->getBeginLoc(),
diff --git a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp 
b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
index 7ae0dcbcfcdb3..c69db73205b12 100644
--- a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
+++ b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
@@ -101,19 +101,6 @@ View both_noescape_and_lifetimebound(
   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}}
-  return identity_lifetimebound(in); // expected-note {{escapes through this 
call}}
-}
-
-View no_annotation_identity(View v) { return v; }
-
-View escape_through_unannotated_call(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
-  return no_annotation_identity(in); // expected-note {{escapes through this 
call}}
-}
-
 View global_view; // expected-note {{escapes to this global storage}}
 
 void escape_through_global_var(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
@@ -151,10 +138,6 @@ struct ObjConsumer {
   View member_view; // expected-note {{escapes to this field}}
 };
 
-void escape_through_param(const MyObj& in [[clang::noescape]], 
std::vector<View> &v) { // expected-warning {{parameter is marked 
[[clang::noescape]] but escapes}}
-  v.push_back(in); // expected-note {{escapes through this call}}
-}
-
 View reassign_to_second(
     const MyObj& a [[clang::noescape]],
     const MyObj& b [[clang::noescape]]) { // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape-strict.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape-strict.cpp
new file mode 100644
index 0000000000000..1aeaddd262c7e
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-noescape-strict.cpp
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference 
-Wlifetime-safety-noescape-strict -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct [[gsl::Owner]] MyObj {
+  int id;
+  ~MyObj() {}
+};
+
+struct [[gsl::Pointer()]] View {
+  View(const MyObj& obj [[clang::noescape]]);
+};
+
+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}}
+  return identity_lifetimebound(in); // expected-note {{escapes through this 
call}}
+}
+
+View no_annotation_identity(View v) { return v; }
+
+View escape_through_unannotated_call(const MyObj& in [[clang::noescape]]) { // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+  return no_annotation_identity(in); // expected-note {{escapes through this 
call}}
+}
+
+void escape_through_param(const MyObj& in [[clang::noescape]], // 
expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+                          std::vector<View> &v) {
+  v.push_back(in); // expected-note {{escapes through this call}}
+}

>From 5ce3454b62367f29de062e6c98942f15700d229e Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Tue, 21 Jul 2026 15:40:06 +1000
Subject: [PATCH 6/7] Add to documentation

---
 clang/docs/LifetimeSafety.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/docs/LifetimeSafety.md b/clang/docs/LifetimeSafety.md
index 0159db898bb73..3623915d56a2f 100644
--- a/clang/docs/LifetimeSafety.md
+++ b/clang/docs/LifetimeSafety.md
@@ -518,6 +518,8 @@ enables only the high-confidence subset of these checks.
   - `-Wlifetime-safety-noescape`: Warns when a parameter marked with 
`[[clang::noescape]]` escapes 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]]`.
 
+- `-Wlifetime-safety-noescape-strict`: More strict version of 
`-Wlifetime-safety-noescape` that also warns when an marked parameter escapes 
through an unannotated function call.
+
 ## Limitations
 
 ### Move Semantics

>From 791828dcbd9a22bad151930a71e49674667b5155 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Tue, 21 Jul 2026 21:36:18 +1000
Subject: [PATCH 7/7] Fix rebase issues

---
 .../clang/Analysis/Analyses/LifetimeSafety/Facts.h   |  4 ++--
 clang/include/clang/Basic/DiagnosticGroups.td        | 12 ++++--------
 clang/lib/Analysis/LifetimeSafety/Facts.cpp          |  3 ++-
 .../noescape-strict.cpp}                             |  0
 4 files changed, 8 insertions(+), 11 deletions(-)
 rename clang/test/Sema/{warn-lifetime-safety-noescape-strict.cpp => 
LifetimeSafety/noescape-strict.cpp} (100%)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index fb6763b227edc..1788958655f7b 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -267,8 +267,8 @@ class CallEscapeFact : public OriginEscapesFact {
   const Expr *getCall() const { return Call; };
   unsigned getArgumentIndex() const { return ArgumentIndex; };
   const Expr *getArgument() const { return Argument; };
-  void dump(llvm::raw_ostream &OS, const LoanManager &,
-            const OriginManager &OM) const override;
+  void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager 
&OM,
+            const LoanPropagationAnalysis *LPA = nullptr) const override;
 };
 
 class UseFact : public Fact {
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index db32d607a6479..e8a2e2174cf1f 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -703,10 +703,6 @@ Detects misuse of [[clang::noescape]] annotation where the 
parameter escapes (fo
   }];
 }
 
-def LifetimeSafetyAnnotationPlacement
-    : DiagGroup<"lifetime-safety-annotation-placement",
-                [LifetimeSafetyInapplicableLifetimebound,
-                 LifetimeSafetyMisplacedLifetimebound]> {
 def LifetimeSafetyNoescapeStrict
     : DiagGroup<"lifetime-safety-noescape-strict", [LifetimeSafetyNoescape]> {
   code Documentation = [{
@@ -714,10 +710,10 @@ Enables stricter detection of [[clang::noescape]] 
annotation misuse (for example
   }];
 }
 
-def LifetimeSafetyValidations : DiagGroup<"lifetime-safety-validations",
-                                          [LifetimeSafetyNoescape,
-                                           
LifetimeSafetyLifetimeboundViolation,
-                                           
LifetimeSafetyMisplacedLifetimebound]> {
+def LifetimeSafetyAnnotationPlacement
+    : DiagGroup<"lifetime-safety-annotation-placement",
+                [LifetimeSafetyInapplicableLifetimebound,
+                 LifetimeSafetyMisplacedLifetimebound]> {
   code Documentation = [{
 Validates that lifetime annotations are placed on appropriate types and in 
appropriate locations.
   }];
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 53bf38bb0b4dc..bbd6baa89d735 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -99,7 +99,8 @@ void GlobalEscapeFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
 }
 
 void CallEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &,
-                          const OriginManager &OM) const {
+                          const OriginManager &OM,
+                          const LoanPropagationAnalysis *) const {
   OS << "CallEscapes (";
   OM.dump(getEscapedOriginID(), OS);
   OS << ", via Call)\n";
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape-strict.cpp 
b/clang/test/Sema/LifetimeSafety/noescape-strict.cpp
similarity index 100%
rename from clang/test/Sema/warn-lifetime-safety-noescape-strict.cpp
rename to clang/test/Sema/LifetimeSafety/noescape-strict.cpp

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

Reply via email to