Author: Baranov Victor
Date: 2026-01-26T18:33:20+03:00
New Revision: a78b8328aa093cd242da16ddead446240b99a769

URL: 
https://github.com/llvm/llvm-project/commit/a78b8328aa093cd242da16ddead446240b99a769
DIFF: 
https://github.com/llvm/llvm-project/commit/a78b8328aa093cd242da16ddead446240b99a769.diff

LOG: [LifetimeSafety] Add report on misuse of clang::noescape (#177260)

Closes https://github.com/llvm/llvm-project/issues/170417.

Added: 
    clang/test/Sema/warn-lifetime-safety-noescape.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Analysis/LifetimeSafety/Checker.cpp
    clang/lib/Sema/AnalysisBasedWarnings.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d03211a200a29..f0d3d81f14e43 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -164,6 +164,24 @@ Improvements to Clang's diagnostics
     int* p(int *in) { return in; }
                              ^~
 
+- Added ``-Wlifetime-safety-noescape`` to detect misuse of 
``[[clang::noescape]]``
+  annotation where the parameter escapes through return. For example:
+
+  .. code-block:: c++
+
+    int* p(int *in [[clang::noescape]]) { return in; }
+
+  Clang will warn:
+
+  .. code-block:: c++
+
+    warning: parameter is marked [[clang::noescape]] but escapes
+    int* p(int *in [[clang::noescape]]) { return in; }
+           ^~~~~~~
+    note: returned here
+    int* p(int *in [[clang::noescape]]) { return in; }
+                                                 ^~
+
 Improvements to Clang's time-trace
 ----------------------------------
 

diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 25d97b4af1ed5..2ab60d918c8d1 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -71,6 +71,10 @@ class LifetimeSafetySemaHelper {
                                              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) {}
+
   // Suggests lifetime bound annotations for implicit this.
   virtual void suggestLifetimeboundToImplicitThis(SuggestionScope Scope,
                                                   const CXXMethodDecl *MD,

diff  --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 34624dd3eed3a..488f3a94c4fb6 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<"lifetime-safety-noescape"> {
+  code Documentation = [{
+    Detects misuse of [[clang::noescape]] annotation where the parameter 
escapes (for example, 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 afe37ab88c5c8..c786eb4486829 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10856,6 +10856,11 @@ def warn_lifetime_safety_cross_tu_this_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">,
+      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 91fbcc0a98650..c6368786f34fe 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -56,6 +56,7 @@ class LifetimeChecker {
 private:
   llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
   llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap;
+  llvm::DenseMap<const ParmVarDecl *, const Expr *> NoescapeWarningsMap;
   const LoanPropagationAnalysis &LoanPropagation;
   const LiveOriginsAnalysis &LiveOrigins;
   const FactManager &FactMgr;
@@ -77,6 +78,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 
diff erentiates between
     //  explicit and inferred findings with separate warning groups.
@@ -85,7 +87,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);
@@ -93,6 +96,10 @@ class LifetimeChecker {
       const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
       if (const auto *PL = dyn_cast<PlaceholderLoan>(L)) {
         if (const auto *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());
@@ -229,6 +236,11 @@ class LifetimeChecker {
     }
   }
 
+  void reportNoescapeViolations() {
+    for (auto [PVD, EscapeExpr] : NoescapeWarningsMap)
+      SemaHelper->reportNoescapeViolation(PVD, EscapeExpr);
+  }
+
   void inferAnnotations() {
     for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
       if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {

diff  --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 39995992eb717..913962dc0c3e0 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2940,6 +2940,17 @@ class LifetimeSafetySemaHelperImpl : public 
LifetimeSafetySemaHelper {
         << 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();
+  }
+
   void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
     S.addLifetimeBoundToImplicitThis(const_cast<CXXMethodDecl *>(MD));
   }
@@ -3088,6 +3099,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
       !Diags.isIgnored(diag::warn_lifetime_safety_return_stack_addr_permissive,
                        D->getBeginLoc()) ||
       !Diags.isIgnored(diag::warn_lifetime_safety_return_stack_addr_strict,
+                       D->getBeginLoc()) ||
+      !Diags.isIgnored(diag::warn_lifetime_safety_noescape_escapes,
                        D->getBeginLoc());
   bool EnableLifetimeSafetyAnalysis =
       S.getLangOpts().EnableLifetimeSafety &&

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..91edd2e33edf8
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -0,0 +1,172 @@
+// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference 
-Wlifetime-safety-noescape -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct [[gsl::Owner]] MyObj {
+  int id;
+  ~MyObj() {}  // Non-trivial destructor
+};
+
+struct [[gsl::Pointer()]] View {
+  View(const MyObj&); // Borrows from MyObj
+  View();
+  void use() const;
+};
+
+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}}
+    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}}
+    const MyObj& b [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
+    bool cond) {
+  if (cond)
+    return a; // expected-note {{returned here}}
+  return b;   // expected-note {{returned here}}
+}
+
+View mixed_noescape_lifetimebound(
+    const MyObj& a [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
+    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}}
+    const MyObj& c [[clang::noescape]], // expected-warning {{parameter is 
marked [[clang::noescape]] but escapes}}
+    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}}
+  return p; // expected-note {{returned here}}
+}
+
+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}}
+  View v = in;
+  return v; // expected-note {{returned here}}
+}
+
+void use_locally(const MyObj& in [[clang::noescape]]) {
+  View v = in;
+  v.use();
+}
+
+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;
+}
+
+// FIXME: diagnose 
diff erently when parameter has both '[[clang::noescape]]' and 
'[[clang::lifetimebound]]'.
+View both_noescape_and_lifetimebound(
+    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}}
+  return identity_lifetimebound(in); // expected-note {{returned here}}
+}
+
+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}}
+}
+
+View global_view;
+
+// FIXME: Escaping through a global variable is not detected.
+void escape_through_global_var(const MyObj& in [[clang::noescape]]) {
+  global_view = in;
+}
+
+// FIXME: Escaping through a member variable is not detected.
+struct ObjConsumer {
+  void escape_through_member(const MyObj& in [[clang::noescape]]) {
+    member_view = in;
+  }
+
+  View member_view;
+};
+
+// FIXME: Escaping through another param is not detected.
+void escape_through_param(const MyObj& in, std::vector<View> &v) {
+  v.push_back(in);
+}
+
+View reassign_to_second(
+    const MyObj& a [[clang::noescape]],
+    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}}
+}
+
+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}}
+  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}}
+  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}}
+  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}}
+  return *p; // expected-note {{returned here}}
+}
+
+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}}
+}


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

Reply via email to