https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/191699
>From 0509d11c4a46716b96e2e271f58e2adf0b701b2e Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 10:11:30 +0000 Subject: [PATCH 1/6] Annotation inference on constructor --- .../Analyses/LifetimeSafety/LifetimeSafety.h | 9 +++-- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 39 +++++++++++++----- clang/lib/Sema/AnalysisBasedWarnings.cpp | 4 ++ clang/lib/Sema/SemaLifetimeSafety.h | 18 ++++++--- .../Sema/warn-lifetime-analysis-nocfg.cpp | 40 +++++++++---------- .../Sema/warn-lifetime-safety-suggestions.cpp | 39 ++++++++++++++++++ clang/test/Sema/warn-lifetime-safety.cpp | 12 +++--- 7 files changed, 113 insertions(+), 48 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 08038dd096685..412c28c6587eb 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -28,6 +28,7 @@ #include "clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h" #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" #include "clang/Analysis/AnalysisDeclContext.h" +#include "llvm/ADT/PointerUnion.h" #include <cstddef> #include <memory> @@ -88,10 +89,10 @@ class LifetimeSafetySemaHelper { const Expr *UseExpr, const Expr *InvalidationExpr) {} - // Suggests lifetime bound annotations for function paramters. - virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope, - const ParmVarDecl *ParmToAnnotate, - const Expr *EscapeExpr) {} + // Suggests lifetime bound annotations for function parameters. + virtual void suggestLifetimeboundToParmVar( + SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, + llvm::PointerUnion<const Expr *, const FieldDecl *> Target) {} // Reports misuse of [[clang::noescape]] when parameter escapes through return virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 36477c6f67b52..3181caff4a5e8 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -59,7 +59,7 @@ using EscapingTarget = class LifetimeChecker { private: llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; - llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap; + llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap; llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap; const LoanPropagationAnalysis &LoanPropagation; const MovedLoansAnalysis &MovedLoans; @@ -67,6 +67,7 @@ class LifetimeChecker { FactManager &FactMgr; LifetimeSafetySemaHelper *SemaHelper; ASTContext &AST; + const Decl *D; static SourceLocation GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) { @@ -89,7 +90,7 @@ class LifetimeChecker { LifetimeSafetySemaHelper *SemaHelper) : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans), LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper), - AST(ADC.getASTContext()) { + AST(ADC.getASTContext()), D(ADC.getDecl()) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs<ExpireFact>()) @@ -126,9 +127,13 @@ class LifetimeChecker { return; } // Suggest lifetimebound for parameter escaping through return. - if (!PVD->hasAttr<LifetimeBoundAttr>()) + if (!PVD->hasAttr<LifetimeBoundAttr>()) { if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr()); + else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF)) + if (isa<CXXConstructorDecl>(D)) + AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl()); + } // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a // field! }; @@ -292,14 +297,22 @@ class LifetimeChecker { static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper, const ParmVarDecl *PVD, SourceManager &SM, - const Expr *EscapeExpr) { + EscapingTarget EscapeTarget) { + llvm::PointerUnion<const Expr *, const FieldDecl *> Target; + if (const auto *E = EscapeTarget.dyn_cast<const Expr *>()) + Target = E; + else if (const auto *F = EscapeTarget.dyn_cast<const FieldDecl *>()) + Target = F; + else + return; + if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM)) SemaHelper->suggestLifetimeboundToParmVar( SuggestionScope::CrossTU, - CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), EscapeExpr); + CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), Target); else SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD, - EscapeExpr); + Target); } static void @@ -319,11 +332,15 @@ class LifetimeChecker { if (!SemaHelper) return; SourceManager &SM = AST.getSourceManager(); - for (auto [Target, EscapeExpr] : AnnotationWarningsMap) { + for (auto [Target, EscapeTarget] : AnnotationWarningsMap) { if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) - suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeExpr); - else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) - suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr); + suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeTarget); + else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) { + if (const auto *EscapeExpr = EscapeTarget.dyn_cast<const Expr *>()) + suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr); + else + llvm_unreachable("Implicit this can only escape via Expr (return)"); + } } } @@ -341,7 +358,7 @@ class LifetimeChecker { } void inferAnnotations() { - for (auto [Target, EscapeExpr] : AnnotationWarningsMap) { + for (auto [Target, EscapeTarget] : AnnotationWarningsMap) { if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) { if (!implicitObjectParamIsLifetimeBound(MD)) SemaHelper->addLifetimeBoundToImplicitThis(cast<CXXMethodDecl>(MD)); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 48957caf61e5a..c538e7cb2211e 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2925,6 +2925,10 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true; AC.getCFGBuildOptions().AddLifetime = true; AC.getCFGBuildOptions().AddParameterLifetimes = true; + AC.getCFGBuildOptions().AddInitializers = true; + AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; + AC.getCFGBuildOptions().AddImplicitDtors = true; + AC.getCFGBuildOptions().AddTemporaryDtors = true; AC.getCFGBuildOptions().setAllAlwaysAdd(); if (AC.getCFG()) runLifetimeSafetyAnalysis(AC, &SemaHelper, LSStats, S.CollectStats); diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index e6f7e3d929f61..add2490be497e 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -131,9 +131,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << UseExpr->getSourceRange(); } - void suggestLifetimeboundToParmVar(SuggestionScope Scope, - const ParmVarDecl *ParmToAnnotate, - const Expr *EscapeExpr) override { + void suggestLifetimeboundToParmVar( + SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, + llvm::PointerUnion<const Expr *, const FieldDecl *> Target) override { unsigned DiagID = (Scope == SuggestionScope::CrossTU) ? diag::warn_lifetime_safety_cross_tu_param_suggestion @@ -150,9 +150,15 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { S.Diag(ParmToAnnotate->getBeginLoc(), DiagID) << ParmToAnnotate->getSourceRange() << FixItHint::CreateInsertion(InsertionPoint, FixItText); - S.Diag(EscapeExpr->getBeginLoc(), - diag::note_lifetime_safety_suggestion_returned_here) - << EscapeExpr->getSourceRange(); + + if (const auto *EscapeExpr = Target.dyn_cast<const Expr *>()) + S.Diag(EscapeExpr->getBeginLoc(), + diag::note_lifetime_safety_suggestion_returned_here) + << EscapeExpr->getSourceRange(); + else if (const auto *EscapeField = Target.dyn_cast<const FieldDecl *>()) + S.Diag(EscapeField->getLocation(), + diag::note_lifetime_safety_escapes_to_field_here) + << EscapeField->getSourceRange(); } void suggestLifetimeboundToImplicitThis(SuggestionScope Scope, diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 0ed151b9db136..2b6c2d3d8bdb6 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -1,7 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s -// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=cfg,cfg-field %s - -// FIXME: cfg-field should be detected in end-of-TU analysis but it doesn't work for constructors! +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=cfg %s // RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=cfg %s #include "Inputs/lifetime-analysis.h" @@ -84,17 +82,17 @@ void dangligGslPtrFromTemporary() { struct DanglingGslPtrField { MyIntPointer p; // expected-note {{pointer member declared here}} \ - // cfg-field-note 3 {{this field dangles}} + // cfg-note 3 {{this field dangles}} MyLongPointerFromConversion p2; // expected-note {{pointer member declared here}} \ - // cfg-field-note 2 {{this field dangles}} + // cfg-note 2 {{this field dangles}} - DanglingGslPtrField(int i) : p(&i) {} // cfg-field-warning {{address of stack memory escapes to a field}} + DanglingGslPtrField(int i) : p(&i) {} // cfg-warning {{address of stack memory escapes to a field}} DanglingGslPtrField() : p2(MyLongOwnerWithConversion{}) {} // expected-warning {{initializing pointer member 'p2' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}} \ - // cfg-field-warning {{address of stack memory escapes to a field}} + // cfg-warning {{address of stack memory escapes to a field}} DanglingGslPtrField(double) : p(MyIntOwner{}) {} // expected-warning {{initializing pointer member 'p' to point to a temporary object whose lifetime is shorter than the lifetime of the constructed object}} \ - // cfg-field-warning {{address of stack memory escapes to a field}} - DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-field-warning {{address of stack memory escapes to a field}} - DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-field-warning {{address of stack memory escapes to a field}} + // cfg-warning {{address of stack memory escapes to a field}} + DanglingGslPtrField(MyIntOwner io) : p(io) {} // cfg-warning {{address of stack memory escapes to a field}} + DanglingGslPtrField(MyLongOwnerWithConversion lo) : p2(lo) {} // cfg-warning {{address of stack memory escapes to a field}} }; MyIntPointer danglingGslPtrFromLocal() { @@ -350,11 +348,11 @@ const char *trackThroughMultiplePointer() { struct X { X(std::unique_ptr<int> up) : - pointee(*up), // cfg-field-warning {{may have been moved.}} - pointee2(up.get()), // cfg-field-warning {{may have been moved.}} - pointer(std::move(up)) {} // cfg-field-note 2 {{potentially moved here}} - int &pointee; // cfg-field-note {{this field dangles}} - int *pointee2; // cfg-field-note {{this field dangles}} + pointee(*up), // cfg-warning {{may have been moved.}} + pointee2(up.get()), // cfg-warning {{may have been moved.}} + pointer(std::move(up)) {} // cfg-note 2 {{potentially moved here}} + int &pointee; // cfg-note {{this field dangles}} + int *pointee2; // cfg-note {{this field dangles}} std::unique_ptr<int> pointer; }; @@ -365,9 +363,9 @@ struct X2 { // A common usage that moves the passing owner to the class. // verify a strict warning on this case. X2(XOwner owner) : - pointee(owner.get()), // cfg-field-warning {{may have been moved.}} - owner(std::move(owner)) {} // cfg-field-note {{potentially moved here}} - int* pointee; // cfg-field-note {{this field dangles}} + pointee(owner.get()), // cfg-warning {{may have been moved.}} + owner(std::move(owner)) {} // cfg-note {{potentially moved here}} + int* pointee; // cfg-note {{this field dangles}} XOwner owner; }; @@ -1116,10 +1114,10 @@ struct Foo2 { }; struct Test { - Test(Foo2 foo) : bar(foo.bar.get()), // cfg-field-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} - storage(std::move(foo.bar)) {}; // cfg-field-note {{potentially moved here}} + Test(Foo2 foo) : bar(foo.bar.get()), // cfg-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} + storage(std::move(foo.bar)) {}; // cfg-note {{potentially moved here}} - Bar* bar; // cfg-field-note {{this field dangles}} + Bar* bar; // cfg-note {{this field dangles}} std::unique_ptr<Bar> storage; }; diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 19c3251b9c296..6ddd03c2ac108 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -456,3 +456,42 @@ S forward(const MyObj &obj) { // expected-warning {{parameter in intra-TU functi } } // namespace track_origins_for_lifetimebound_record_type + +namespace capturing_constructor { +struct A { + View v; // expected-note {{escapes to this field}} + A(const MyObj& obj) : v(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +struct B { + const int* p; // expected-note {{escapes to this field}} + B(const int& r) : p(&r) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +struct C { + View v; // expected-note {{escapes to this field}} + C(View v_param) : v(v_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +struct D { + const int* p; // expected-note {{escapes to this field}} + D(const int* p_param) : p(p_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; +} // namespace capturing_constructor + +namespace capturing_constructor_inference { +struct B { + const MyObj* p; // expected-note {{escapes to this field}} + B(const MyObj& obj) : p(&obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +void test(B& b_out) { + { + MyObj obj; + B b(obj); // expected-warning {{object whose reference is captured does not live long enough}} + b_out = b; + } // expected-note {{destroyed here}} + (void)b_out; // expected-note {{later used here}} +} +} // namespace capturing_constructor_inference + diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 77d8e3370676d..b1dea7f7340b3 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -2275,8 +2275,8 @@ void from_template_instantiation() { } struct FieldInitFromLifetimebound { - S value; // function-note {{this field dangles}} - FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // function-warning {{address of stack memory escapes to a field}} + S value; // expected-note {{this field dangles}} + FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // expected-warning {{address of stack memory escapes to a field}} }; S S::return_self_after_registration() const { @@ -2467,13 +2467,13 @@ void nested_local_pointer() { } struct PFieldFromParam { - Pointer<Bar> value; // function-note {{this field dangles}} - PFieldFromParam(Bar bar) : value(bar) {} // function-warning {{address of stack memory escapes to a field}} + Pointer<Bar> value; // expected-note {{this field dangles}} + PFieldFromParam(Bar bar) : value(bar) {} // expected-warning {{address of stack memory escapes to a field}} }; struct PFieldFromTemp { - Pointer<Bar> value; // function-note {{this field dangles}} - PFieldFromTemp() : value(Bar{}) {} // function-warning {{address of stack memory escapes to a field}} + Pointer<Bar> value; // expected-note {{this field dangles}} + PFieldFromTemp() : value(Bar{}) {} // expected-warning {{address of stack memory escapes to a field}} }; } // namespace gslpointer_construction_from_lifetimebound >From 341f87f2ded30376b15bae62de4098ae6177acff Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:25:11 +0000 Subject: [PATCH 2/6] refactor --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 3181caff4a5e8..2f92622da33ad 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -67,7 +67,7 @@ class LifetimeChecker { FactManager &FactMgr; LifetimeSafetySemaHelper *SemaHelper; ASTContext &AST; - const Decl *D; + const Decl *FD; static SourceLocation GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) { @@ -90,7 +90,7 @@ class LifetimeChecker { LifetimeSafetySemaHelper *SemaHelper) : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans), LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper), - AST(ADC.getASTContext()), D(ADC.getDecl()) { + AST(ADC.getASTContext()), FD(ADC.getDecl()) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs<ExpireFact>()) @@ -130,9 +130,9 @@ class LifetimeChecker { if (!PVD->hasAttr<LifetimeBoundAttr>()) { if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr()); - else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF)) - if (isa<CXXConstructorDecl>(D)) - AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl()); + else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF); + FieldEsc && isa<CXXConstructorDecl>(FD)) + AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl()); } // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a // field! >From 501bc05510bcceef2ff3634f2569a18ffd01b81c Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:34:46 +0000 Subject: [PATCH 3/6] Refactor EscapingTarget --- .../Analyses/LifetimeSafety/LifetimeSafety.h | 9 ++++++--- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 15 +++++---------- clang/lib/Sema/SemaLifetimeSafety.h | 6 +++--- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 412c28c6587eb..83c3c455c4c81 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -89,10 +89,13 @@ class LifetimeSafetySemaHelper { const Expr *UseExpr, const Expr *InvalidationExpr) {} + using EscapingTarget = + llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>; + // Suggests lifetime bound annotations for function parameters. - virtual void suggestLifetimeboundToParmVar( - SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, - llvm::PointerUnion<const Expr *, const FieldDecl *> Target) {} + virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope, + const ParmVarDecl *ParmToAnnotate, + EscapingTarget Target) {} // Reports misuse of [[clang::noescape]] when parameter escapes through return virtual void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape, diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 2f92622da33ad..f82caedcbe044 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -53,8 +53,7 @@ struct PendingWarning { using AnnotationTarget = llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>; -using EscapingTarget = - llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>; +using EscapingTarget = LifetimeSafetySemaHelper::EscapingTarget; class LifetimeChecker { private: @@ -298,21 +297,17 @@ class LifetimeChecker { const ParmVarDecl *PVD, SourceManager &SM, EscapingTarget EscapeTarget) { - llvm::PointerUnion<const Expr *, const FieldDecl *> Target; - if (const auto *E = EscapeTarget.dyn_cast<const Expr *>()) - Target = E; - else if (const auto *F = EscapeTarget.dyn_cast<const FieldDecl *>()) - Target = F; - else + if (llvm::isa<const VarDecl *>(EscapeTarget)) return; if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM)) SemaHelper->suggestLifetimeboundToParmVar( SuggestionScope::CrossTU, - CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), Target); + CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), + EscapeTarget); else SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD, - Target); + EscapeTarget); } static void diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index add2490be497e..c56a9692abe1a 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -131,9 +131,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { << UseExpr->getSourceRange(); } - void suggestLifetimeboundToParmVar( - SuggestionScope Scope, const ParmVarDecl *ParmToAnnotate, - llvm::PointerUnion<const Expr *, const FieldDecl *> Target) override { + void suggestLifetimeboundToParmVar(SuggestionScope Scope, + const ParmVarDecl *ParmToAnnotate, + EscapingTarget Target) override { unsigned DiagID = (Scope == SuggestionScope::CrossTU) ? diag::warn_lifetime_safety_cross_tu_param_suggestion >From 52cdb9585fb6257d0e9b65dc8290b67790d9cb16 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:38:15 +0000 Subject: [PATCH 4/6] doc update --- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index f82caedcbe044..fe103739dbcb0 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -125,7 +125,8 @@ class LifetimeChecker { NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal()); return; } - // Suggest lifetimebound for parameter escaping through return. + // Suggest lifetimebound for parameter escaping through return or a field + // in constructor. if (!PVD->hasAttr<LifetimeBoundAttr>()) { if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF)) AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr()); >From e4e360574ff0a448237390828fa4817c06624ef8 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 12 Apr 2026 11:48:52 +0000 Subject: [PATCH 5/6] more tests --- .../Sema/warn-lifetime-safety-suggestions.cpp | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 6ddd03c2ac108..c96b92cfe0b5b 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -458,40 +458,59 @@ S forward(const MyObj &obj) { // expected-warning {{parameter in intra-TU functi } // namespace track_origins_for_lifetimebound_record_type namespace capturing_constructor { -struct A { +struct CaptureRefToView { View v; // expected-note {{escapes to this field}} - A(const MyObj& obj) : v(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + CaptureRefToView(const MyObj& obj) : v(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -struct B { - const int* p; // expected-note {{escapes to this field}} - B(const int& r) : p(&r) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +CaptureRefToView test_ref_to_view() { + MyObj obj; + CaptureRefToView x(obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} +} + +struct CaptureRefToPtr { + const MyObj* p; // expected-note {{escapes to this field}} + CaptureRefToPtr(const MyObj& obj) : p(&obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -struct C { +CaptureRefToPtr test_ref_to_ptr() { + MyObj obj; + CaptureRefToPtr x(obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} +} + +struct CaptureViewToView { View v; // expected-note {{escapes to this field}} - C(View v_param) : v(v_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + CaptureViewToView(View v_param) : v(v_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -struct D { - const int* p; // expected-note {{escapes to this field}} - D(const int* p_param) : p(p_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} -}; -} // namespace capturing_constructor +CaptureViewToView test_view_to_view() { + MyObj obj; + View v(obj); // expected-warning {{address of stack memory is returned later}} + CaptureViewToView x(v); + return x; // expected-note {{returned here}} +} -namespace capturing_constructor_inference { -struct B { +struct CapturePtrToPtr { const MyObj* p; // expected-note {{escapes to this field}} - B(const MyObj& obj) : p(&obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + CapturePtrToPtr(const MyObj* p_param) : p(p_param) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} }; -void test(B& b_out) { - { - MyObj obj; - B b(obj); // expected-warning {{object whose reference is captured does not live long enough}} - b_out = b; - } // expected-note {{destroyed here}} - (void)b_out; // expected-note {{later used here}} +CapturePtrToPtr test_ptr_to_ptr() { + MyObj obj; + CapturePtrToPtr x(&obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} } -} // namespace capturing_constructor_inference +struct CaptureRefToRef { + const MyObj& r; // expected-note {{escapes to this field}} + CaptureRefToRef(const MyObj& obj) : r(obj) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} +}; + +CaptureRefToRef test_ref_to_ref() { + MyObj obj; + CaptureRefToRef x(obj); // expected-warning {{address of stack memory is returned later}} + return x; // expected-note {{returned here}} +} +} // namespace capturing_constructor >From f34ea95a6027a43fb462aa1667251cc425341142 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Mon, 13 Apr 2026 09:25:45 +0000 Subject: [PATCH 6/6] remove dtors from cfg --- clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index c538e7cb2211e..2b30aad880a27 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2927,8 +2927,6 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, AC.getCFGBuildOptions().AddParameterLifetimes = true; AC.getCFGBuildOptions().AddInitializers = true; AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; - AC.getCFGBuildOptions().AddImplicitDtors = true; - AC.getCFGBuildOptions().AddTemporaryDtors = true; AC.getCFGBuildOptions().setAllAlwaysAdd(); if (AC.getCFG()) runLifetimeSafetyAnalysis(AC, &SemaHelper, LSStats, S.CollectStats); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
