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

>From 45b8ecad6e32b1e70edd5b58d701d07e54087ce0 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Fri, 13 Mar 2026 00:21:23 +1000
Subject: [PATCH 1/2] 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 42a71fb5a50d2..11ab374a5e82c 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"
@@ -153,6 +155,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) {
@@ -222,6 +225,40 @@ class GlobalEscapeFact : public OriginEscapesFact {
             const OriginManager &OM) 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 4ffc8b4195949..b03b3f7ec8a0f 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -76,6 +76,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 {
   OS << "Use (";

>From afda40ff78df07d5794680b4ca592387f93e335d Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Sat, 21 Mar 2026 22:52:08 +1000
Subject: [PATCH 2/2] 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 +++
 .../Sema/warn-lifetime-safety-noescape.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 11ab374a5e82c..f969609206c2c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -232,29 +232,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 7399fa1c2dbd2..e6534a13fb776 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -113,6 +113,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;
       }
       // Suggest lifetimebound for parameter escaping through return.
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8238cf69edfcd..fa84551d62492 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -14,12 +14,14 @@
 #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"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "clang/Analysis/CFG.h"
+#include "clang/Basic/SourceManager.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Casting.h"
@@ -577,13 +579,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);
   handleMovedArgsInCall(FD, Args);
   if (!CallList)
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp 
b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index fe20d779669c1..9d5f220751118 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/warn-lifetime-safety-noescape.cpp 
b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index f233ec546faa5..08f12af156836 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.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(

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

Reply via email to