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/5] [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/5] 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/5] 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/5] 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/5] 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 { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
