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
