https://github.com/vbvictor updated https://github.com/llvm/llvm-project/pull/177260
>From af5debc62026f37b686644b467b239b6f730d1df Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Thu, 22 Jan 2026 01:18:49 +0300 Subject: [PATCH 01/17] [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 34624dd3eed3a..20665a908d616 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 33461284e11dd..ad52d5f779507 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10844,6 +10844,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 03d84fc935b8e..f941918f5c158 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 99dcd227113a53f40f0dc4a555182b0e2df4e36d Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Thu, 22 Jan 2026 01:24:04 +0300 Subject: [PATCH 02/17] 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 949fcb624f398d5e6844daa8cdb47aba5533b251 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sat, 24 Jan 2026 14:11:36 +0300 Subject: [PATCH 03/17] 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 20665a908d616..f26d9985be19a 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 ad52d5f779507..806de5a0a819f 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10845,8 +10845,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 f941918f5c158..7eb125d5c5b24 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 101c7c1edf2d50b5228cf3a6628baaf2768d9638 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sat, 24 Jan 2026 14:16:05 +0300 Subject: [PATCH 04/17] 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 f26d9985be19a..20665a908d616 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 fb3c6e9a6eb3de7ef8652d97fb48af89b2f5d72b Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sat, 24 Jan 2026 16:20:17 +0300 Subject: [PATCH 05/17] 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 { >From b964726a79ada8ec88954eaff1ae545e6b35255b Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sat, 24 Jan 2026 17:59:42 +0300 Subject: [PATCH 06/17] handle [[ ]] correctly --- clang/lib/Sema/AnalysisBasedWarnings.cpp | 31 ++++++++++++++++++- .../Sema/warn-lifetime-safety-noescape.cpp | 4 +++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 7eb125d5c5b24..5246f77a06c95 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2930,11 +2930,40 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter { void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, const Expr *EscapeExpr) override { const auto *Attr = ParmWithNoescape->getAttr<NoEscapeAttr>(); + SourceRange RemovalRange = Attr->getRange(); + + // For [[clang::noescape]], Attr->getRange() only covers the inner + // 'clang::noescape' part. Extend to include the '[[' and ']]' brackets. + if (Attr->isStandardAttributeSyntax()) { + const SourceManager &SM = S.getSourceManager(); + const LangOptions &LO = S.getLangOpts(); + + auto SecondOpen = Lexer::findPreviousToken( + Attr->getRange().getBegin(), SM, LO, /*IncludeComments=*/false); + auto FirstOpen = + SecondOpen && SecondOpen->is(tok::l_square) + ? Lexer::findPreviousToken(SecondOpen->getLocation(), SM, LO, + /*IncludeComments=*/false) + : std::nullopt; + + auto FirstClose = Lexer::findNextToken(Attr->getRange().getEnd(), SM, LO, + /*IncludeComments=*/false); + auto SecondClose = + FirstClose && FirstClose->is(tok::r_square) + ? Lexer::findNextToken(FirstClose->getLocation(), SM, LO, + /*IncludeComments=*/false) + : std::nullopt; + + if ((FirstOpen && FirstOpen->is(tok::l_square)) && + (SecondClose && SecondClose->is(tok::r_square))) + RemovalRange = {FirstOpen->getLocation(), SecondClose->getLocation()}; + } S.Diag(ParmWithNoescape->getBeginLoc(), diag::warn_lifetime_safety_noescape_escapes) << ParmWithNoescape->getSourceRange() - << FixItHint::CreateRemoval(Attr->getRange()); + << FixItHint::CreateRemoval(RemovalRange); + 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 8e0a27536234b..300c616de1feb 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -167,3 +167,7 @@ View construct_but_return_other(const MyObj& in [[clang::noescape]]) { MyObj other; return other; } + +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}} +} >From aca1492047175c4fbcaf87623e83e813f4d8e620 Mon Sep 17 00:00:00 2001 From: Baranov Victor <[email protected]> Date: Sat, 24 Jan 2026 19:19:08 +0300 Subject: [PATCH 07/17] Apply suggestion from @vbvictor --- clang/test/Sema/warn-lifetime-safety-noescape.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index 300c616de1feb..cf20cbc4ec849 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s +// RUN: %clang_cc1 -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 %t // RUN: %clang_cc1 -x c++ -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wno-dangling -Werror %t >From 7d727665f2b42445781f61e91642e76a737821f0 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sun, 25 Jan 2026 11:33:13 +0300 Subject: [PATCH 08/17] fix build? --- .../Sema/warn-lifetime-safety-noescape.cpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index cf20cbc4ec849..9f991d88ef345 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s +// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wexperimental-lifetime-safety -Wno-dangling -verify %s // RUN: cp %s %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 +// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wexperimental-lifetime-safety -Wno-dangling -fixit %t +// RUN: %clang_cc1 -x c++ -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wexperimental-lifetime-safety -Wno-dangling -Werror %t struct [[gsl::Owner]] MyObj { int id; @@ -83,12 +83,6 @@ void use_locally(const MyObj& in [[clang::noescape]]) { 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; } @@ -161,13 +155,6 @@ MyObj& return_ref_from_noescape_ptr( 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; -} - 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}} } >From a9c2d2c2ed9a62b0fedb23e0e5a75505db999b1a Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sun, 25 Jan 2026 13:43:24 +0300 Subject: [PATCH 09/17] only bare minimum --- clang/test/Sema/warn-lifetime-safety-noescape.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index 9f991d88ef345..beca877255c0c 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,7 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wexperimental-lifetime-safety -Wno-dangling -verify %s -// RUN: cp %s %t -// RUN: %clang_cc1 -x c++ -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wexperimental-lifetime-safety -Wno-dangling -fixit %t -// RUN: %clang_cc1 -x c++ -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wexperimental-lifetime-safety -Wno-dangling -Werror %t +// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -Wno-dangling -verify %s struct [[gsl::Owner]] MyObj { int id; >From 21f9a4d60ecdca2804c111058756ad9351c774e3 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sun, 25 Jan 2026 13:45:26 +0300 Subject: [PATCH 10/17] bare minimum 2 --- clang/test/Sema/warn-lifetime-safety-noescape.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index beca877255c0c..eed7842506fc0 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -Wno-dangling -verify %s +// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s struct [[gsl::Owner]] MyObj { int id; >From e54c85aa4f10d874de014068d48ab204e0b9807e Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sun, 25 Jan 2026 14:04:14 +0300 Subject: [PATCH 11/17] fix final build.. --- clang/include/clang/Basic/DiagnosticGroups.td | 2 +- clang/test/Sema/warn-lifetime-safety-noescape.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 20665a908d616..3113d0fa618be 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -554,7 +554,7 @@ def LifetimeSafetySuggestions }]; } def LifetimeSafetyNoescape - : DiagGroup<"experimental-lifetime-safety-noescape"> { + : DiagGroup<"lifetime-safety-noescape"> { code Documentation = [{ Detects misuse of [[clang::noescape]] annotation where the parameter escapes through return. }]; diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index eed7842506fc0..a34caf6077dc7 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,4 +1,8 @@ -// RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety -Wexperimental-lifetime-safety -Wexperimental-lifetime-safety-noescape -Wno-dangling -verify %s +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-noescape -Wlifetime-safety -Wno-dangling -verify %s +// RUN: cp %s %t +// RUN: %clang_cc1 -x c++ -Wlifetime-safety-noescape -Wlifetime-safety -Wno-dangling -fixit %t +// RUN: %clang_cc1 -x c++ -fsyntax-only -Wlifetime-safety-noescape -Wlifetime-safety -Wno-dangling -Werror %t + struct [[gsl::Owner]] MyObj { int id; >From f99056282d2334dafbfef8b47550eb943564938f Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sun, 25 Jan 2026 14:12:25 +0300 Subject: [PATCH 12/17] Add release notes --- clang/docs/ReleaseNotes.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 14384ea3b51c1..22ab4219a9361 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -159,6 +159,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 ---------------------------------- >From b3b39b9c7447d9552d821667ab5e4cfa85eda299 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Sun, 25 Jan 2026 23:20:49 +0300 Subject: [PATCH 13/17] remove fixits --- clang/lib/Sema/AnalysisBasedWarnings.cpp | 33 +------------------ .../Sema/warn-lifetime-safety-noescape.cpp | 4 --- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 5246f77a06c95..f941918f5c158 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2929,40 +2929,9 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter { void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, const Expr *EscapeExpr) override { - const auto *Attr = ParmWithNoescape->getAttr<NoEscapeAttr>(); - SourceRange RemovalRange = Attr->getRange(); - - // For [[clang::noescape]], Attr->getRange() only covers the inner - // 'clang::noescape' part. Extend to include the '[[' and ']]' brackets. - if (Attr->isStandardAttributeSyntax()) { - const SourceManager &SM = S.getSourceManager(); - const LangOptions &LO = S.getLangOpts(); - - auto SecondOpen = Lexer::findPreviousToken( - Attr->getRange().getBegin(), SM, LO, /*IncludeComments=*/false); - auto FirstOpen = - SecondOpen && SecondOpen->is(tok::l_square) - ? Lexer::findPreviousToken(SecondOpen->getLocation(), SM, LO, - /*IncludeComments=*/false) - : std::nullopt; - - auto FirstClose = Lexer::findNextToken(Attr->getRange().getEnd(), SM, LO, - /*IncludeComments=*/false); - auto SecondClose = - FirstClose && FirstClose->is(tok::r_square) - ? Lexer::findNextToken(FirstClose->getLocation(), SM, LO, - /*IncludeComments=*/false) - : std::nullopt; - - if ((FirstOpen && FirstOpen->is(tok::l_square)) && - (SecondClose && SecondClose->is(tok::r_square))) - RemovalRange = {FirstOpen->getLocation(), SecondClose->getLocation()}; - } - S.Diag(ParmWithNoescape->getBeginLoc(), diag::warn_lifetime_safety_noescape_escapes) - << ParmWithNoescape->getSourceRange() - << FixItHint::CreateRemoval(RemovalRange); + << ParmWithNoescape->getSourceRange(); S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_safety_suggestion_returned_here) diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index a34caf6077dc7..b6f29b1f43feb 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,8 +1,4 @@ // RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-noescape -Wlifetime-safety -Wno-dangling -verify %s -// RUN: cp %s %t -// RUN: %clang_cc1 -x c++ -Wlifetime-safety-noescape -Wlifetime-safety -Wno-dangling -fixit %t -// RUN: %clang_cc1 -x c++ -fsyntax-only -Wlifetime-safety-noescape -Wlifetime-safety -Wno-dangling -Werror %t - struct [[gsl::Owner]] MyObj { int id; >From 1b2170b777d56c83404542246bb19164798d7572 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Mon, 26 Jan 2026 16:00:33 +0300 Subject: [PATCH 14/17] pr fixes --- clang/include/clang/Basic/DiagnosticGroups.td | 2 +- .../Sema/warn-lifetime-safety-noescape.cpp | 29 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 3113d0fa618be..488f3a94c4fb6 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -556,7 +556,7 @@ def LifetimeSafetySuggestions def LifetimeSafetyNoescape : DiagGroup<"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 (for example, through return). }]; } diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index b6f29b1f43feb..90f81a71189ab 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 -Wlifetime-safety-noescape -Wlifetime-safety -Wno-dangling -verify %s +// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -Wlifetime-safety-noescape -Wlifetime-safety -verify %s + +#include "Inputs/lifetime-analysis.h" struct [[gsl::Owner]] MyObj { int id; @@ -92,7 +94,7 @@ void pointer_used_locally(MyObj* p [[clang::noescape]]) { p->id = 42; } -// Noescape should take precedence and warn since the parameter does escape. +// FIXME: diagnose differently 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}} @@ -107,16 +109,29 @@ View escape_through_lifetimebound_call( 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); +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 view; +View global_view; // FIXME: Escaping through a global variable is not detected. void escape_through_global_var(const MyObj& in [[clang::noescape]]) { - view = in; + 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( >From ddfa950bba2680b798420d6d5abd45e3a3ca41ae Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Mon, 26 Jan 2026 16:19:00 +0300 Subject: [PATCH 15/17] fix format --- clang/lib/Sema/AnalysisBasedWarnings.cpp | 615 ++++++++++++----------- 1 file changed, 314 insertions(+), 301 deletions(-) diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index ef136f567474a..e64eb6b760c76 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -70,60 +70,61 @@ using namespace clang; //===----------------------------------------------------------------------===// namespace { -class UnreachableCodeHandler : public reachable_code::Callback { - Sema &S; - SourceRange PreviousSilenceableCondVal; - -public: - UnreachableCodeHandler(Sema &s) : S(s) {} - - void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L, - SourceRange SilenceableCondVal, SourceRange R1, - SourceRange R2, bool HasFallThroughAttr) override { - // If the diagnosed code is `[[fallthrough]];` and - // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never - // be executed` warning to avoid generating diagnostic twice - if (HasFallThroughAttr && - !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr, - SourceLocation())) - return; + class UnreachableCodeHandler : public reachable_code::Callback { + Sema &S; + SourceRange PreviousSilenceableCondVal; + + public: + UnreachableCodeHandler(Sema &s) : S(s) {} + + void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L, + SourceRange SilenceableCondVal, SourceRange R1, + SourceRange R2, bool HasFallThroughAttr) override { + // If the diagnosed code is `[[fallthrough]];` and + // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never + // be executed` warning to avoid generating diagnostic twice + if (HasFallThroughAttr && + !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr, + SourceLocation())) + return; - // Avoid reporting multiple unreachable code diagnostics that are - // triggered by the same conditional value. - if (PreviousSilenceableCondVal.isValid() && SilenceableCondVal.isValid() && - PreviousSilenceableCondVal == SilenceableCondVal) - return; - PreviousSilenceableCondVal = SilenceableCondVal; + // Avoid reporting multiple unreachable code diagnostics that are + // triggered by the same conditional value. + if (PreviousSilenceableCondVal.isValid() && + SilenceableCondVal.isValid() && + PreviousSilenceableCondVal == SilenceableCondVal) + return; + PreviousSilenceableCondVal = SilenceableCondVal; - unsigned diag = diag::warn_unreachable; - switch (UK) { - case reachable_code::UK_Break: - diag = diag::warn_unreachable_break; - break; - case reachable_code::UK_Return: - diag = diag::warn_unreachable_return; - break; - case reachable_code::UK_Loop_Increment: - diag = diag::warn_unreachable_loop_increment; - break; - case reachable_code::UK_Other: - break; - } + unsigned diag = diag::warn_unreachable; + switch (UK) { + case reachable_code::UK_Break: + diag = diag::warn_unreachable_break; + break; + case reachable_code::UK_Return: + diag = diag::warn_unreachable_return; + break; + case reachable_code::UK_Loop_Increment: + diag = diag::warn_unreachable_loop_increment; + break; + case reachable_code::UK_Other: + break; + } - S.Diag(L, diag) << R1 << R2; + S.Diag(L, diag) << R1 << R2; - SourceLocation Open = SilenceableCondVal.getBegin(); - if (Open.isValid()) { - SourceLocation Close = SilenceableCondVal.getEnd(); - Close = S.getLocForEndOfToken(Close); - if (Close.isValid()) { - S.Diag(Open, diag::note_unreachable_silence) + SourceLocation Open = SilenceableCondVal.getBegin(); + if (Open.isValid()) { + SourceLocation Close = SilenceableCondVal.getEnd(); + Close = S.getLocForEndOfToken(Close); + if (Close.isValid()) { + S.Diag(Open, diag::note_unreachable_silence) << FixItHint::CreateInsertion(Open, "/* DISABLES CODE */ (") << FixItHint::CreateInsertion(Close, ")"); + } } } - } -}; + }; } // anonymous namespace /// CheckUnreachable - Check for unreachable code. @@ -293,8 +294,7 @@ static void checkRecursiveFunction(Sema &S, const FunctionDecl *FD, return; CFG *cfg = AC.getCFG(); - if (!cfg) - return; + if (!cfg) return; // If the exit block is unreachable, skip processing the function. if (cfg->getExit().pred_empty()) @@ -329,9 +329,10 @@ static bool throwEscapes(Sema &S, const CXXThrowExpr *E, CFGBlock &ThrowBlock, if (Succ->getBlockID() == Body->getExit().getBlockID()) return true; - if (auto *Catch = dyn_cast_or_null<CXXCatchStmt>(Succ->getLabel())) { + if (auto *Catch = + dyn_cast_or_null<CXXCatchStmt>(Succ->getLabel())) { QualType Caught = Catch->getCaughtType(); - if (Caught.isNull() || // catch (...) catches everything + if (Caught.isNull() || // catch (...) catches everything !E->getSubExpr() || // throw; is considered cuaght by any handler S.handlerCanCatch(Caught, E->getSubExpr()->getType())) // Exception doesn't escape via this path. @@ -350,8 +351,7 @@ static void visitReachableThrows( CFG *BodyCFG, llvm::function_ref<void(const CXXThrowExpr *, CFGBlock &)> Visit) { llvm::BitVector Reachable(BodyCFG->getNumBlockIDs()); - clang::reachable_code::ScanReachableFromBlock(&BodyCFG->getEntry(), - Reachable); + clang::reachable_code::ScanReachableFromBlock(&BodyCFG->getEntry(), Reachable); for (CFGBlock *B : *BodyCFG) { if (!Reachable[B->getBlockID()]) continue; @@ -374,8 +374,8 @@ static void EmitDiagForCXXThrowInNonThrowingFunc(Sema &S, SourceLocation OpLoc, (isa<CXXDestructorDecl>(FD) || FD->getDeclName().getCXXOverloadedOperator() == OO_Delete || FD->getDeclName().getCXXOverloadedOperator() == OO_Array_Delete)) { - if (const auto *Ty = - FD->getTypeSourceInfo()->getType()->getAs<FunctionProtoType>()) + if (const auto *Ty = FD->getTypeSourceInfo()->getType()-> + getAs<FunctionProtoType>()) S.Diag(FD->getLocation(), diag::note_throw_in_dtor) << !isa<CXXDestructorDecl>(FD) << !Ty->hasExceptionSpec() << FD->getExceptionSpecSourceRange(); @@ -392,11 +392,10 @@ static void checkThrowInNonThrowingFunc(Sema &S, const FunctionDecl *FD, return; if (BodyCFG->getExit().pred_empty()) return; - visitReachableThrows( - BodyCFG, [&](const CXXThrowExpr *Throw, CFGBlock &Block) { - if (throwEscapes(S, Throw, Block, BodyCFG)) - EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD); - }); + visitReachableThrows(BodyCFG, [&](const CXXThrowExpr *Throw, CFGBlock &Block) { + if (throwEscapes(S, Throw, Block, BodyCFG)) + EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD); + }); } static bool isNoexcept(const FunctionDecl *FD) { @@ -569,14 +568,13 @@ enum ControlFlowKind { /// will return. static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) { CFG *cfg = AC.getCFG(); - if (!cfg) - return UnknownFallThrough; + if (!cfg) return UnknownFallThrough; // The CFG leaves in dead things, and we don't want the dead code paths to // confuse us, so we mark all live things first. llvm::BitVector live(cfg->getNumBlockIDs()); - unsigned count = - reachable_code::ScanReachableFromBlock(&cfg->getEntry(), live); + unsigned count = reachable_code::ScanReachableFromBlock(&cfg->getEntry(), + live); bool AddEHEdges = AC.getAddEHEdges(); if (!AddEHEdges && count != cfg->getNumBlockIDs()) @@ -629,7 +627,7 @@ static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) { // statement (if it exists). CFGBlock::const_reverse_iterator ri = B.rbegin(), re = B.rend(); - for (; ri != re; ++ri) + for ( ; ri != re ; ++ri) if (ri->getAs<CFGStmt>()) break; @@ -803,12 +801,14 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body, else ReturnsVoid = FD->getReturnType()->isVoidType(); HasNoReturn = FD->isNoReturn() || FD->hasAttr<InferredNoReturnAttr>(); - } else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) { + } + else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) { ReturnsVoid = MD->getReturnType()->isVoidType(); HasNoReturn = MD->hasAttr<NoReturnAttr>(); - } else if (isa<BlockDecl>(D)) { + } + else if (isa<BlockDecl>(D)) { if (const FunctionType *FT = - BlockType->getPointeeType()->getAs<FunctionType>()) { + BlockType->getPointeeType()->getAs<FunctionType>()) { if (FT->getReturnType()->isVoidType()) ReturnsVoid = true; if (FT->getNoReturnAttr()) @@ -820,7 +820,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body, // Short circuit for compilation speed. if (CD.checkDiagnostics(Diags, ReturnsVoid, HasNoReturn)) - return; + return; SourceLocation LBrace = Body->getBeginLoc(), RBrace = Body->getEndLoc(); // cpu_dispatch functions permit empty function bodies for ICC compatibility. @@ -897,7 +897,7 @@ class ContainsReference : public ConstEvaluatedExprVisitor<ContainsReference> { typedef ConstEvaluatedExprVisitor<ContainsReference> Inherited; ContainsReference(ASTContext &Context, const DeclRefExpr *Needle) - : Inherited(Context), FoundReference(false), Needle(Needle) {} + : Inherited(Context), FoundReference(false), Needle(Needle) {} void VisitExpr(const Expr *E) { // Stop evaluating if we already have a reference. @@ -920,7 +920,8 @@ class ContainsReference : public ConstEvaluatedExprVisitor<ContainsReference> { static bool SuggestInitializationFixit(Sema &S, const VarDecl *VD) { QualType VariableTy = VD->getType().getCanonicalType(); - if (VariableTy->isBlockPointerType() && !VD->hasAttr<BlocksAttr>()) { + if (VariableTy->isBlockPointerType() && + !VD->hasAttr<BlocksAttr>()) { S.Diag(VD->getLocation(), diag::note_block_var_fixit_add_initialization) << VD->getDeclName() << FixItHint::CreateInsertion(VD->getLocation(), "__block "); @@ -942,16 +943,16 @@ static bool SuggestInitializationFixit(Sema &S, const VarDecl *VD) { if (Init.empty()) return false; - S.Diag(Loc, diag::note_var_fixit_add_initialization) - << VD->getDeclName() << FixItHint::CreateInsertion(Loc, Init); + S.Diag(Loc, diag::note_var_fixit_add_initialization) << VD->getDeclName() + << FixItHint::CreateInsertion(Loc, Init); return true; } /// Create a fixit to remove an if-like statement, on the assumption that its /// condition is CondVal. static void CreateIfFixit(Sema &S, const Stmt *If, const Stmt *Then, - const Stmt *Else, bool CondVal, FixItHint &Fixit1, - FixItHint &Fixit2) { + const Stmt *Else, bool CondVal, + FixItHint &Fixit1, FixItHint &Fixit2) { if (CondVal) { // If condition is always true, remove all but the 'then'. Fixit1 = FixItHint::CreateRemoval( @@ -1018,9 +1019,9 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use, // For all binary terminators, branch 0 is taken if the condition is true, // and branch 1 is taken if the condition is false. int RemoveDiagKind = -1; - const char *FixitStr = S.getLangOpts().CPlusPlus - ? (I->Output ? "true" : "false") - : (I->Output ? "1" : "0"); + const char *FixitStr = + S.getLangOpts().CPlusPlus ? (I->Output ? "true" : "false") + : (I->Output ? "1" : "0"); FixItHint Fixit1, Fixit2; switch (Term ? Term->getStmtClass() : Stmt::DeclStmtClass) { @@ -1036,8 +1037,8 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use, Str = "if"; Range = IS->getCond()->getSourceRange(); RemoveDiagKind = 0; - CreateIfFixit(S, IS, IS->getThen(), IS->getElse(), I->Output, Fixit1, - Fixit2); + CreateIfFixit(S, IS, IS->getThen(), IS->getElse(), + I->Output, Fixit1, Fixit2); break; } case Stmt::ConditionalOperatorClass: { @@ -1046,8 +1047,8 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use, Str = "?:"; Range = CO->getCond()->getSourceRange(); RemoveDiagKind = 0; - CreateIfFixit(S, CO, CO->getTrueExpr(), CO->getFalseExpr(), I->Output, - Fixit1, Fixit2); + CreateIfFixit(S, CO, CO->getTrueExpr(), CO->getFalseExpr(), + I->Output, Fixit1, Fixit2); break; } case Stmt::BinaryOperatorClass: { @@ -1122,13 +1123,13 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use, } S.Diag(Range.getBegin(), diag::warn_sometimes_uninit_var) - << VD->getDeclName() << IsCapturedByBlock << DiagKind << Str - << I->Output << Range; + << VD->getDeclName() << IsCapturedByBlock << DiagKind + << Str << I->Output << Range; S.Diag(User->getBeginLoc(), diag::note_uninit_var_use) << IsCapturedByBlock << User->getSourceRange(); if (RemoveDiagKind != -1) S.Diag(Fixit1.RemoveRange.getBegin(), diag::note_uninit_fixit_remove_cond) - << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2; + << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2; Diagnosed = true; } @@ -1298,32 +1299,32 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor { // Don't care about other unreachable statements. } } - // If there are no unreachable statements, this may be a special - // case in CFG: - // case X: { - // A a; // A has a destructor. - // break; - // } - // // <<<< This place is represented by a 'hanging' CFG block. - // case Y: - continue; + // If there are no unreachable statements, this may be a special + // case in CFG: + // case X: { + // A a; // A has a destructor. + // break; + // } + // // <<<< This place is represented by a 'hanging' CFG block. + // case Y: + continue; } - const Stmt *LastStmt = getLastStmt(*P); - if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) { - markFallthroughVisited(AS); - ++AnnotatedCnt; - continue; // Fallthrough annotation, good. - } + const Stmt *LastStmt = getLastStmt(*P); + if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) { + markFallthroughVisited(AS); + ++AnnotatedCnt; + continue; // Fallthrough annotation, good. + } - if (!LastStmt) { // This block contains no executable statements. - // Traverse its predecessors. - std::copy(P->pred_begin(), P->pred_end(), - std::back_inserter(BlockQueue)); - continue; - } + if (!LastStmt) { // This block contains no executable statements. + // Traverse its predecessors. + std::copy(P->pred_begin(), P->pred_end(), + std::back_inserter(BlockQueue)); + continue; + } - ++UnannotatedCnt; + ++UnannotatedCnt; } return !!UnannotatedCnt; } @@ -1339,63 +1340,64 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor { return true; } - // We don't want to traverse local type declarations. We analyze their - // methods separately. - bool TraverseDecl(Decl *D) override { return true; } + // We don't want to traverse local type declarations. We analyze their + // methods separately. + bool TraverseDecl(Decl *D) override { return true; } - // We analyze lambda bodies separately. Skip them here. - bool TraverseLambdaExpr(LambdaExpr *LE) override { - // Traverse the captures, but not the body. - for (const auto C : zip(LE->captures(), LE->capture_inits())) - TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C)); - return true; - } + // We analyze lambda bodies separately. Skip them here. + bool TraverseLambdaExpr(LambdaExpr *LE) override { + // Traverse the captures, but not the body. + for (const auto C : zip(LE->captures(), LE->capture_inits())) + TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C)); + return true; + } -private: - static const AttributedStmt *asFallThroughAttr(const Stmt *S) { - if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) { - if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs())) - return AS; + private: + + static const AttributedStmt *asFallThroughAttr(const Stmt *S) { + if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) { + if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs())) + return AS; + } + return nullptr; } - return nullptr; - } - static const Stmt *getLastStmt(const CFGBlock &B) { - if (const Stmt *Term = B.getTerminatorStmt()) - return Term; - for (const CFGElement &Elem : llvm::reverse(B)) - if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>()) - return CS->getStmt(); - // Workaround to detect a statement thrown out by CFGBuilder: - // case X: {} case Y: - // case X: ; case Y: - if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel())) - if (!isa<SwitchCase>(SW->getSubStmt())) - return SW->getSubStmt(); + static const Stmt *getLastStmt(const CFGBlock &B) { + if (const Stmt *Term = B.getTerminatorStmt()) + return Term; + for (const CFGElement &Elem : llvm::reverse(B)) + if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>()) + return CS->getStmt(); + // Workaround to detect a statement thrown out by CFGBuilder: + // case X: {} case Y: + // case X: ; case Y: + if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel())) + if (!isa<SwitchCase>(SW->getSubStmt())) + return SW->getSubStmt(); - return nullptr; - } + return nullptr; + } - bool FoundSwitchStatements; - AttrStmts FallthroughStmts; - Sema &S; - llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks; + bool FoundSwitchStatements; + AttrStmts FallthroughStmts; + Sema &S; + llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks; }; } // anonymous namespace static StringRef getFallthroughAttrSpelling(Preprocessor &PP, SourceLocation Loc) { - TokenValue FallthroughTokens[] = {tok::l_square, tok::l_square, - PP.getIdentifierInfo("fallthrough"), - tok::r_square, tok::r_square}; - - TokenValue ClangFallthroughTokens[] = {tok::l_square, - tok::l_square, - PP.getIdentifierInfo("clang"), - tok::coloncolon, - PP.getIdentifierInfo("fallthrough"), - tok::r_square, - tok::r_square}; + TokenValue FallthroughTokens[] = { + tok::l_square, tok::l_square, + PP.getIdentifierInfo("fallthrough"), + tok::r_square, tok::r_square + }; + + TokenValue ClangFallthroughTokens[] = { + tok::l_square, tok::l_square, PP.getIdentifierInfo("clang"), + tok::coloncolon, PP.getIdentifierInfo("fallthrough"), + tok::r_square, tok::r_square + }; bool PreferClangAttr = !PP.getLangOpts().CPlusPlus17 && !PP.getLangOpts().C23; @@ -1510,12 +1512,13 @@ static bool isInLoop(const ASTContext &Ctx, const ParentMap &PM, static void diagnoseRepeatedUseOfWeak(Sema &S, const sema::FunctionScopeInfo *CurFn, - const Decl *D, const ParentMap &PM) { + const Decl *D, + const ParentMap &PM) { typedef sema::FunctionScopeInfo::WeakObjectProfileTy WeakObjectProfileTy; typedef sema::FunctionScopeInfo::WeakObjectUseMap WeakObjectUseMap; typedef sema::FunctionScopeInfo::WeakUseVector WeakUseVector; typedef std::pair<const Stmt *, WeakObjectUseMap::const_iterator> - StmtUsesPair; + StmtUsesPair; ASTContext &Ctx = S.getASTContext(); @@ -1529,7 +1532,7 @@ static void diagnoseRepeatedUseOfWeak(Sema &S, // Find the first read of the weak object. WeakUseVector::const_iterator UI = Uses.begin(), UE = Uses.end(); - for (; UI != UE; ++UI) { + for ( ; UI != UE; ++UI) { if (UI->isUnsafe()) break; } @@ -1586,7 +1589,12 @@ static void diagnoseRepeatedUseOfWeak(Sema &S, // warn_arc_repeated_use_of_weak and warn_arc_possible_repeated_use_of_weak. // FIXME: Should we use a common classification enum and the same set of // possibilities all throughout Sema? - enum { Function, Method, Block, Lambda } FunctionKind; + enum { + Function, + Method, + Block, + Lambda + } FunctionKind; if (isa<sema::BlockScopeInfo>(CurFn)) FunctionKind = Block; @@ -1617,7 +1625,12 @@ static void diagnoseRepeatedUseOfWeak(Sema &S, // Classify the weak object being accessed for better warning text. // This enum should stay in sync with the cases in // warn_arc_repeated_use_of_weak and warn_arc_possible_repeated_use_of_weak. - enum { Variable, Property, ImplicitProperty, Ivar } ObjectKind; + enum { + Variable, + Property, + ImplicitProperty, + Ivar + } ObjectKind; const NamedDecl *KeyProp = Key.getProperty(); if (isa<VarDecl>(KeyProp)) @@ -1719,7 +1732,7 @@ class UninitValsDiagReporter : public UninitVariablesHandler { } private: - static bool hasAlwaysUninitializedUse(const UsesVec *vec) { + static bool hasAlwaysUninitializedUse(const UsesVec* vec) { return llvm::any_of(*vec, [](const UninitUse &U) { return U.getKind() == UninitUse::Always || U.getKind() == UninitUse::AfterCall || @@ -1969,10 +1982,10 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { : getNotes(); } -public: + public: ThreadSafetyReporter(Sema &S, SourceLocation FL, SourceLocation FEL) - : S(S), FunLocation(FL), FunEndLocation(FEL), CurrentFunction(nullptr), - Verbose(false) {} + : S(S), FunLocation(FL), FunEndLocation(FEL), + CurrentFunction(nullptr), Verbose(false) {} void setVerbose(bool b) { Verbose = b; } @@ -2067,18 +2080,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { bool ReentrancyMismatch) override { unsigned DiagID = 0; switch (LEK) { - case LEK_LockedSomePredecessors: - DiagID = diag::warn_lock_some_predecessors; - break; - case LEK_LockedSomeLoopIterations: - DiagID = diag::warn_expecting_lock_held_on_loop; - break; - case LEK_LockedAtEndOfFunction: - DiagID = diag::warn_no_unlock; - break; - case LEK_NotLockedAtEndOfFunction: - DiagID = diag::warn_expecting_locked; - break; + case LEK_LockedSomePredecessors: + DiagID = diag::warn_lock_some_predecessors; + break; + case LEK_LockedSomeLoopIterations: + DiagID = diag::warn_expecting_lock_held_on_loop; + break; + case LEK_LockedAtEndOfFunction: + DiagID = diag::warn_no_unlock; + break; + case LEK_NotLockedAtEndOfFunction: + DiagID = diag::warn_expecting_locked; + break; } if (LocEndOfScope.isInvalid()) LocEndOfScope = FunEndLocation; @@ -2124,7 +2137,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { break; } PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) - << D << getLockKindFromAccessKind(AK)); + << D << getLockKindFromAccessKind(AK)); Warnings.emplace_back(std::move(Warning), getNotes()); } @@ -2135,42 +2148,43 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { unsigned DiagID = 0; if (PossibleMatch) { switch (POK) { - case POK_VarAccess: - DiagID = diag::warn_variable_requires_lock_precise; - break; - case POK_VarDereference: - DiagID = diag::warn_var_deref_requires_lock_precise; - break; - case POK_FunctionCall: - DiagID = diag::warn_fun_requires_lock_precise; - break; - case POK_PassByRef: - DiagID = diag::warn_guarded_pass_by_reference; - break; - case POK_PtPassByRef: - DiagID = diag::warn_pt_guarded_pass_by_reference; - break; - case POK_ReturnByRef: - DiagID = diag::warn_guarded_return_by_reference; - break; - case POK_PtReturnByRef: - DiagID = diag::warn_pt_guarded_return_by_reference; - break; - case POK_PassPointer: - DiagID = diag::warn_guarded_pass_pointer; - break; - case POK_PtPassPointer: - DiagID = diag::warn_pt_guarded_pass_pointer; - break; - case POK_ReturnPointer: - DiagID = diag::warn_guarded_return_pointer; - break; - case POK_PtReturnPointer: - DiagID = diag::warn_pt_guarded_return_pointer; - break; + case POK_VarAccess: + DiagID = diag::warn_variable_requires_lock_precise; + break; + case POK_VarDereference: + DiagID = diag::warn_var_deref_requires_lock_precise; + break; + case POK_FunctionCall: + DiagID = diag::warn_fun_requires_lock_precise; + break; + case POK_PassByRef: + DiagID = diag::warn_guarded_pass_by_reference; + break; + case POK_PtPassByRef: + DiagID = diag::warn_pt_guarded_pass_by_reference; + break; + case POK_ReturnByRef: + DiagID = diag::warn_guarded_return_by_reference; + break; + case POK_PtReturnByRef: + DiagID = diag::warn_pt_guarded_return_by_reference; + break; + case POK_PassPointer: + DiagID = diag::warn_guarded_pass_pointer; + break; + case POK_PtPassPointer: + DiagID = diag::warn_pt_guarded_pass_pointer; + break; + case POK_ReturnPointer: + DiagID = diag::warn_guarded_return_pointer; + break; + case POK_PtReturnPointer: + DiagID = diag::warn_pt_guarded_return_pointer; + break; } - PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) - << Kind << D << LockName << LK); + PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind + << D + << LockName << LK); PartialDiagnosticAt Note(Loc, S.PDiag(diag::note_found_mutex_near_match) << *PossibleMatch); if (Verbose && POK == POK_VarAccess) { @@ -2182,42 +2196,43 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { Warnings.emplace_back(std::move(Warning), getNotes(Note)); } else { switch (POK) { - case POK_VarAccess: - DiagID = diag::warn_variable_requires_lock; - break; - case POK_VarDereference: - DiagID = diag::warn_var_deref_requires_lock; - break; - case POK_FunctionCall: - DiagID = diag::warn_fun_requires_lock; - break; - case POK_PassByRef: - DiagID = diag::warn_guarded_pass_by_reference; - break; - case POK_PtPassByRef: - DiagID = diag::warn_pt_guarded_pass_by_reference; - break; - case POK_ReturnByRef: - DiagID = diag::warn_guarded_return_by_reference; - break; - case POK_PtReturnByRef: - DiagID = diag::warn_pt_guarded_return_by_reference; - break; - case POK_PassPointer: - DiagID = diag::warn_guarded_pass_pointer; - break; - case POK_PtPassPointer: - DiagID = diag::warn_pt_guarded_pass_pointer; - break; - case POK_ReturnPointer: - DiagID = diag::warn_guarded_return_pointer; - break; - case POK_PtReturnPointer: - DiagID = diag::warn_pt_guarded_return_pointer; - break; + case POK_VarAccess: + DiagID = diag::warn_variable_requires_lock; + break; + case POK_VarDereference: + DiagID = diag::warn_var_deref_requires_lock; + break; + case POK_FunctionCall: + DiagID = diag::warn_fun_requires_lock; + break; + case POK_PassByRef: + DiagID = diag::warn_guarded_pass_by_reference; + break; + case POK_PtPassByRef: + DiagID = diag::warn_pt_guarded_pass_by_reference; + break; + case POK_ReturnByRef: + DiagID = diag::warn_guarded_return_by_reference; + break; + case POK_PtReturnByRef: + DiagID = diag::warn_pt_guarded_return_by_reference; + break; + case POK_PassPointer: + DiagID = diag::warn_guarded_pass_pointer; + break; + case POK_PtPassPointer: + DiagID = diag::warn_pt_guarded_pass_pointer; + break; + case POK_ReturnPointer: + DiagID = diag::warn_guarded_return_pointer; + break; + case POK_PtReturnPointer: + DiagID = diag::warn_pt_guarded_return_pointer; + break; } - PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) - << Kind << D << LockName << LK); + PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind + << D + << LockName << LK); if (Verbose && POK == POK_VarAccess) { PartialDiagnosticAt Note(D->getLocation(), S.PDiag(diag::note_guarded_by_declared_here)); @@ -2229,9 +2244,9 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { void handleNegativeNotHeld(StringRef Kind, Name LockName, Name Neg, SourceLocation Loc) override { - PartialDiagnosticAt Warning( - Loc, S.PDiag(diag::warn_acquire_requires_negative_cap) - << Kind << LockName << Neg); + PartialDiagnosticAt Warning(Loc, + S.PDiag(diag::warn_acquire_requires_negative_cap) + << Kind << LockName << Neg); Warnings.emplace_back(std::move(Warning), getNotes()); } @@ -2251,20 +2266,22 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { void handleLockAcquiredBefore(StringRef Kind, Name L1Name, Name L2Name, SourceLocation Loc) override { - PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_acquired_before) - << Kind << L1Name << L2Name); + PartialDiagnosticAt Warning(Loc, + S.PDiag(diag::warn_acquired_before) << Kind << L1Name << L2Name); Warnings.emplace_back(std::move(Warning), getNotes()); } void handleBeforeAfterCycle(Name L1Name, SourceLocation Loc) override { - PartialDiagnosticAt Warning( - Loc, S.PDiag(diag::warn_acquired_before_after_cycle) << L1Name); + PartialDiagnosticAt Warning(Loc, + S.PDiag(diag::warn_acquired_before_after_cycle) << L1Name); Warnings.emplace_back(std::move(Warning), getNotes()); } - void enterFunction(const FunctionDecl *FD) override { CurrentFunction = FD; } + void enterFunction(const FunctionDecl* FD) override { + CurrentFunction = FD; + } - void leaveFunction(const FunctionDecl *FD) override { + void leaveFunction(const FunctionDecl* FD) override { CurrentFunction = nullptr; } }; @@ -2285,6 +2302,7 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { DiagList Warnings; public: + ConsumedWarningsHandler(Sema &S) : S(S) {} void emitDiagnostics() override { @@ -2298,8 +2316,8 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { void warnLoopStateMismatch(SourceLocation Loc, StringRef VariableName) override { - PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_loop_state_mismatch) - << VariableName); + PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_loop_state_mismatch) << + VariableName); Warnings.emplace_back(std::move(Warning), OptionalNotes()); } @@ -2309,9 +2327,9 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { StringRef ExpectedState, StringRef ObservedState) override { - PartialDiagnosticAt Warning( - Loc, S.PDiag(diag::warn_param_return_typestate_mismatch) - << VariableName << ExpectedState << ObservedState); + PartialDiagnosticAt Warning(Loc, S.PDiag( + diag::warn_param_return_typestate_mismatch) << VariableName << + ExpectedState << ObservedState); Warnings.emplace_back(std::move(Warning), OptionalNotes()); } @@ -2319,18 +2337,16 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { void warnParamTypestateMismatch(SourceLocation Loc, StringRef ExpectedState, StringRef ObservedState) override { - PartialDiagnosticAt Warning(Loc, - S.PDiag(diag::warn_param_typestate_mismatch) - << ExpectedState << ObservedState); + PartialDiagnosticAt Warning(Loc, S.PDiag( + diag::warn_param_typestate_mismatch) << ExpectedState << ObservedState); Warnings.emplace_back(std::move(Warning), OptionalNotes()); } void warnReturnTypestateForUnconsumableType(SourceLocation Loc, StringRef TypeName) override { - PartialDiagnosticAt Warning( - Loc, S.PDiag(diag::warn_return_typestate_for_unconsumable_type) - << TypeName); + PartialDiagnosticAt Warning(Loc, S.PDiag( + diag::warn_return_typestate_for_unconsumable_type) << TypeName); Warnings.emplace_back(std::move(Warning), OptionalNotes()); } @@ -2338,9 +2354,8 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { void warnReturnTypestateMismatch(SourceLocation Loc, StringRef ExpectedState, StringRef ObservedState) override { - PartialDiagnosticAt Warning(Loc, - S.PDiag(diag::warn_return_typestate_mismatch) - << ExpectedState << ObservedState); + PartialDiagnosticAt Warning(Loc, S.PDiag( + diag::warn_return_typestate_mismatch) << ExpectedState << ObservedState); Warnings.emplace_back(std::move(Warning), OptionalNotes()); } @@ -2348,9 +2363,8 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { void warnUseOfTempInInvalidState(StringRef MethodName, StringRef State, SourceLocation Loc) override { - PartialDiagnosticAt Warning(Loc, - S.PDiag(diag::warn_use_of_temp_in_invalid_state) - << MethodName << State); + PartialDiagnosticAt Warning(Loc, S.PDiag( + diag::warn_use_of_temp_in_invalid_state) << MethodName << State); Warnings.emplace_back(std::move(Warning), OptionalNotes()); } @@ -2358,9 +2372,8 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { void warnUseInInvalidState(StringRef MethodName, StringRef VariableName, StringRef State, SourceLocation Loc) override { - PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_use_in_invalid_state) - << MethodName << VariableName - << State); + PartialDiagnosticAt Warning(Loc, S.PDiag(diag::warn_use_in_invalid_state) << + MethodName << VariableName << State); Warnings.emplace_back(std::move(Warning), OptionalNotes()); } @@ -2376,7 +2389,7 @@ class ConsumedWarningsHandler : public ConsumedWarningsHandlerBase { namespace { class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { Sema &S; - bool SuggestSuggestions; // Recommend -fsafe-buffer-usage-suggestions? + bool SuggestSuggestions; // Recommend -fsafe-buffer-usage-suggestions? // Lists as a string the names of variables in `VarGroupForVD` except for `VD` // itself: @@ -2415,7 +2428,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { public: UnsafeBufferUsageReporter(Sema &S, bool SuggestSuggestions) - : S(S), SuggestSuggestions(SuggestSuggestions) {} + : S(S), SuggestSuggestions(SuggestSuggestions) {} void handleUnsafeOperation(const Stmt *Operation, bool IsRelatedToDecl, ASTContext &Ctx) override { @@ -2600,7 +2613,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { #ifndef NDEBUG if (areDebugNotesRequested()) - for (const DebugNote &Note : DebugNotesByVar[Variable]) + for (const DebugNote &Note: DebugNotesByVar[Variable]) S.Diag(Note.first, diag::note_safe_buffer_debug_mode) << Note.second; #endif } @@ -2702,7 +2715,8 @@ sema::AnalysisBasedWarnings::AnalysisBasedWarnings(Sema &s) MaxCFGBlocksPerFunction(0), NumUninitAnalysisFunctions(0), NumUninitAnalysisVariables(0), MaxUninitAnalysisVariablesPerFunction(0), NumUninitAnalysisBlockVisits(0), - MaxUninitAnalysisBlockVisitsPerFunction(0) {} + MaxUninitAnalysisBlockVisitsPerFunction(0) { +} // We need this here for unique_ptr with forward declared class. sema::AnalysisBasedWarnings::~AnalysisBasedWarnings() = default; @@ -2974,7 +2988,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, } void clang::sema::AnalysisBasedWarnings::IssueWarnings( - TranslationUnitDecl *TU) { + TranslationUnitDecl *TU) { if (!TU) return; // This is unexpected, give up quietly. @@ -2988,7 +3002,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( // UnsafeBufferUsage analysis settings. bool UnsafeBufferUsageCanEmitSuggestions = S.getLangOpts().CPlusPlus20; - bool UnsafeBufferUsageShouldEmitSuggestions = // Should != Can. + bool UnsafeBufferUsageShouldEmitSuggestions = // Should != Can. UnsafeBufferUsageCanEmitSuggestions && DiagOpts.ShowSafeBufferUsageSuggestions; bool UnsafeBufferUsageShouldSuggestSuggestions = @@ -3103,13 +3117,13 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( AC.getCFGBuildOptions().setAllAlwaysAdd(); } else { AC.getCFGBuildOptions() - .setAlwaysAdd(Stmt::BinaryOperatorClass) - .setAlwaysAdd(Stmt::CompoundAssignOperatorClass) - .setAlwaysAdd(Stmt::BlockExprClass) - .setAlwaysAdd(Stmt::CStyleCastExprClass) - .setAlwaysAdd(Stmt::DeclRefExprClass) - .setAlwaysAdd(Stmt::ImplicitCastExprClass) - .setAlwaysAdd(Stmt::UnaryOperatorClass); + .setAlwaysAdd(Stmt::BinaryOperatorClass) + .setAlwaysAdd(Stmt::CompoundAssignOperatorClass) + .setAlwaysAdd(Stmt::BlockExprClass) + .setAlwaysAdd(Stmt::CStyleCastExprClass) + .setAlwaysAdd(Stmt::DeclRefExprClass) + .setAlwaysAdd(Stmt::ImplicitCastExprClass) + .setAlwaysAdd(Stmt::UnaryOperatorClass); } if (EnableLifetimeSafetyAnalysis) AC.getCFGBuildOptions().AddLifetime = true; @@ -3190,10 +3204,12 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( ++NumUninitAnalysisFunctions; NumUninitAnalysisVariables += stats.NumVariablesAnalyzed; NumUninitAnalysisBlockVisits += stats.NumBlockVisits; - MaxUninitAnalysisVariablesPerFunction = std::max( - MaxUninitAnalysisVariablesPerFunction, stats.NumVariablesAnalyzed); - MaxUninitAnalysisBlockVisitsPerFunction = std::max( - MaxUninitAnalysisBlockVisitsPerFunction, stats.NumBlockVisits); + MaxUninitAnalysisVariablesPerFunction = + std::max(MaxUninitAnalysisVariablesPerFunction, + stats.NumVariablesAnalyzed); + MaxUninitAnalysisBlockVisitsPerFunction = + std::max(MaxUninitAnalysisBlockVisitsPerFunction, + stats.NumBlockVisits); } } } @@ -3231,6 +3247,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( !Diags.isIgnored(diag::warn_arc_repeated_use_of_weak, D->getBeginLoc())) diagnoseRepeatedUseOfWeak(S, fscope, D, AC.getParentMap()); + // Check for infinite self-recursion in functions if (!Diags.isIgnored(diag::warn_infinite_recursive_function, D->getBeginLoc())) { @@ -3261,8 +3278,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( // If we successfully built a CFG for this context, record some more // detail information about it. NumCFGBlocks += cfg->getNumBlockIDs(); - MaxCFGBlocksPerFunction = - std::max(MaxCFGBlocksPerFunction, cfg->getNumBlockIDs()); + MaxCFGBlocksPerFunction = std::max(MaxCFGBlocksPerFunction, + cfg->getNumBlockIDs()); } else { ++NumFunctionsWithBadCFGs; } @@ -3274,7 +3291,7 @@ void clang::sema::AnalysisBasedWarnings::PrintStats() const { unsigned NumCFGsBuilt = NumFunctionsAnalyzed - NumFunctionsWithBadCFGs; unsigned AvgCFGBlocksPerFunction = - !NumCFGsBuilt ? 0 : NumCFGBlocks / NumCFGsBuilt; + !NumCFGsBuilt ? 0 : NumCFGBlocks/NumCFGsBuilt; llvm::errs() << NumFunctionsAnalyzed << " functions analyzed (" << NumFunctionsWithBadCFGs << " w/o CFGs).\n" << " " << NumCFGBlocks << " CFG blocks built.\n" @@ -3283,14 +3300,10 @@ void clang::sema::AnalysisBasedWarnings::PrintStats() const { << " " << MaxCFGBlocksPerFunction << " max CFG blocks per function.\n"; - unsigned AvgUninitVariablesPerFunction = - !NumUninitAnalysisFunctions - ? 0 - : NumUninitAnalysisVariables / NumUninitAnalysisFunctions; - unsigned AvgUninitBlockVisitsPerFunction = - !NumUninitAnalysisFunctions - ? 0 - : NumUninitAnalysisBlockVisits / NumUninitAnalysisFunctions; + unsigned AvgUninitVariablesPerFunction = !NumUninitAnalysisFunctions ? 0 + : NumUninitAnalysisVariables/NumUninitAnalysisFunctions; + unsigned AvgUninitBlockVisitsPerFunction = !NumUninitAnalysisFunctions ? 0 + : NumUninitAnalysisBlockVisits/NumUninitAnalysisFunctions; llvm::errs() << NumUninitAnalysisFunctions << " functions analyzed for uninitialiazed variables\n" << " " << NumUninitAnalysisVariables << " variables analyzed.\n" >From 5d8471810be86fc33d55d1e99dc1c1f7a151acc9 Mon Sep 17 00:00:00 2001 From: Victor Baranov <[email protected]> Date: Mon, 26 Jan 2026 16:28:31 +0300 Subject: [PATCH 16/17] add noescape to IsLifetimeSafetyDiagnosticEnabled --- clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 ++ clang/test/Sema/warn-lifetime-safety-noescape.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index e64eb6b760c76..913962dc0c3e0 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -3099,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 index 90f81a71189ab..46c3a6753111c 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -Wlifetime-safety-noescape -Wlifetime-safety -verify %s +// RUN: %clang_cc1 -fsyntax-only -flifetime-safety -flifetime-safety-inference -Wlifetime-safety-noescape -verify %s #include "Inputs/lifetime-analysis.h" >From 6aa683f41eb3776d2c19fa9965198cf8dc2152b8 Mon Sep 17 00:00:00 2001 From: Baranov Victor <[email protected]> Date: Mon, 26 Jan 2026 17:45:30 +0300 Subject: [PATCH 17/17] Apply suggestion from @vbvictor --- clang/test/Sema/warn-lifetime-safety-noescape.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index 46c3a6753111c..91edd2e33edf8 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -flifetime-safety -flifetime-safety-inference -Wlifetime-safety-noescape -verify %s +// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -Wlifetime-safety-noescape -verify %s #include "Inputs/lifetime-analysis.h" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
