[llvm-branch-commits] [clang] [LifetimeSafety] Suggest/infer annotation in constructors (PR #191699)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Apr 13 02:39:02 PDT 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/191699
>From 36cc5af4d82c6a71281976cbbff16c9672d641e1 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 | 41 ++++++++++++++++++-
clang/test/Sema/warn-lifetime-safety.cpp | 12 +++---
7 files changed, 114 insertions(+), 49 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 c352cca2023a6..e07c90f248764 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() {
@@ -361,11 +359,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;
};
@@ -376,9 +374,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;
};
@@ -1127,10 +1125,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 5906170b0d146..4f62dd608e814 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -462,7 +462,8 @@ S forward(const MyObj &obj) { // expected-warning {{parameter in intra-TU functi
namespace make_unique_suggestion {
struct LifetimeBoundCtor {
- LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
+ const MyObj& obj;
+ LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]) : obj(obj) {}
};
std::unique_ptr<LifetimeBoundCtor> create_target(const MyObj& obj) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
@@ -478,3 +479,41 @@ void test_inference() {
(void)ptr; // expected-note {{later used here}}
}
} // namespace make_unique_suggestion
+
+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 e0147d69459f5..07562c4b33f8e 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2361,8 +2361,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 {
@@ -2551,13 +2551,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 2dc6dd7f6a61a9e7af37025fbec84c8b3844143e Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 17d50549d173a4904bf9c08c82599ba09e4a7d5d Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 c73e396dc8330e3aaa5a0540b28ac8ca51b0985e Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 f2e9ba03a24e9bc58528e5ad0f753efdcb016d49 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 12 Apr 2026 11:48:52 +0000
Subject: [PATCH 5/6] more tests
---
.../Sema/warn-lifetime-safety-suggestions.cpp | 66 ++++++++++++-------
1 file changed, 43 insertions(+), 23 deletions(-)
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 4f62dd608e814..ee3ed7f385faf 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -481,39 +481,59 @@ void test_inference() {
} // namespace make_unique_suggestion
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}}
+}
+
+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_inference
+} // namespace capturing_constructor
>From 6504df805cb892ff9b45a653e0f09dcf5190b5e5 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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);
More information about the llvm-branch-commits
mailing list