[clang] [LifetimeSafety] More support for member expressions (PR #191865)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 16 03:33:02 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-analysis
Author: Utkarsh Saxena (usx95)
<details>
<summary>Changes</summary>
Extended lifetime safety analysis to support member expressions in assignment operations, enabling detection of dangling references through field assignments.
---
Full diff: https://github.com/llvm/llvm-project/pull/191865.diff
6 Files Affected:
- (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h (+1-1)
- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+17-9)
- (modified) clang/lib/Analysis/LifetimeSafety/Origins.cpp (+1-1)
- (modified) clang/test/Sema/warn-lifetime-safety-dangling-field.cpp (+17-12)
- (modified) clang/test/Sema/warn-lifetime-safety-invalidations.cpp (+19)
- (modified) clang/test/Sema/warn-lifetime-safety.cpp (+5-4)
``````````diff
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index a86f22a181a70..1edbd8311ee58 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -121,7 +121,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
// (e.g. on the left-hand side of an assignment in the case of a DeclRefExpr).
void handleUse(const Expr *E);
- void markUseAsWrite(const DeclRefExpr *DRE);
+ void markUseAsWrite(const Expr *E);
bool escapesViaReturn(OriginID OID) const;
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 5adf842e5f6d0..476701ec6dffc 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -259,6 +259,7 @@ void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
assert(Dst && "Field member should have an origin list as it is GL value");
OriginList *Src = getOriginsList(*ME->getBase());
assert(Src && "Base expression should be a pointer/reference type");
+ handleUse(ME);
// The field's glvalue (outermost origin) holds the same loans as the base
// expression.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
@@ -386,28 +387,35 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
// assigned.
RHSList = getRValueOrigins(RHSExpr, RHSList);
- if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
- QualType QT = DRE_LHS->getDecl()->getType();
+ const ValueDecl *VD = nullptr;
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(LHSExpr))
+ VD = DRE->getDecl();
+ else if (const auto *ME = dyn_cast<MemberExpr>(LHSExpr))
+ VD = ME->getMemberDecl();
+
+ if (VD) {
+ QualType QT = VD->getType();
if (QT->isReferenceType()) {
if (hasOrigins(QT->getPointeeType())) {
// Writing through a reference uses the binding but overwrites the
// pointee. Model this as a Read of the outer origin (keeping the
// binding live) and a Write of the inner origins (killing the pointee's
// liveness).
- if (UseFact *UF = UseFacts.lookup(DRE_LHS)) {
+ if (UseFact *UF = UseFacts.lookup(LHSExpr)) {
const OriginList *FullList = UF->getUsedOrigins();
assert(FullList);
UF->setUsedOrigins(FactMgr.getOriginMgr().createSingleOriginList(
FullList->getOuterOriginID()));
if (const OriginList *InnerList = FullList->peelOuterOrigin()) {
- UseFact *WriteUF = FactMgr.createFact<UseFact>(DRE_LHS, InnerList);
+ UseFact *WriteUF = FactMgr.createFact<UseFact>(LHSExpr, InnerList);
WriteUF->markAsWritten();
CurrentBlockFacts.push_back(WriteUF);
}
}
}
- } else
- markUseAsWrite(DRE_LHS);
+ } else {
+ markUseAsWrite(LHSExpr);
+ }
}
if (!RHSList) {
// RHS has no tracked origins (e.g., assigning a callable without origins
@@ -925,9 +933,9 @@ void FactsGenerator::handleUse(const Expr *E) {
}
}
-void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
- if (UseFacts.contains(DRE))
- UseFacts[DRE]->markAsWritten();
+void FactsGenerator::markUseAsWrite(const Expr *E) {
+ if (UseFacts.contains(E))
+ UseFacts[E]->markAsWritten();
}
// Creates an IssueFact for a new placeholder loan for each pointer or reference
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 033cbdd75352c..505ae5146d5ba 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -240,7 +240,7 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
ReferencedDecl = DRE->getDecl();
else if (auto *ME = dyn_cast<MemberExpr>(E))
if (auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
- Field && isa<CXXThisExpr>(ME->getBase()->IgnoreParenImpCasts()))
+ Field && isa<CXXThisExpr>(ME->getBase()->IgnoreImpCasts()))
ReferencedDecl = Field;
if (ReferencedDecl) {
OriginList *Head = nullptr;
diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
index 2afcd4a3dd97b..51159f561d5a7 100644
--- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -68,6 +68,11 @@ struct CtorRefField {
CtorRefField(Dummy<1> ok, const std::string& s, const std::string_view& v): str(s), view(v) {}
};
+struct CtorRefFieldTemporary {
+ const std::string& str; // expected-note {{this field dangles}}
+ CtorRefFieldTemporary(): str(std::vector<std::string>{"abcd"}[0]) {} // expected-warning {{address of stack memory escapes to a field}}
+};
+
struct CtorPointerField {
const char* ptr; // expected-note {{this field dangles}}
CtorPointerField(std::string s) : ptr(s.data()) {} // expected-warning {{address of stack memory escapes to a field}}
@@ -76,8 +81,8 @@ struct CtorPointerField {
};
struct MemberSetters {
- std::string_view view; // expected-note 6 {{this field dangles}}
- const char* p; // expected-note 6 {{this field dangles}}
+ std::string_view view; // expected-note 5 {{this field dangles}}
+ const char* p; // expected-note 5 {{this field dangles}}
void setWithParam(std::string s) {
view = s; // expected-warning {{address of stack memory escapes to a field}}
@@ -140,21 +145,21 @@ struct MemberSetters {
void use_after_scope() {
{
std::string local;
- view = local; // expected-warning {{address of stack memory escapes to a field}}
- p = local.data(); // expected-warning {{address of stack memory escapes to a field}}
- }
- (void)view;
- (void)p;
+ view = local; // expected-warning {{object whose reference is captured does not live long enough}}
+ p = local.data(); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ (void)view; // expected-note {{later used here}}
+ (void)p; // expected-note {{later used here}}
}
void use_after_scope_saved_after_reassignment() {
{
std::string local;
- view = local;
- p = local.data();
- }
- (void)view;
- (void)p;
+ view = local; // expected-warning {{object whose reference is captured does not live long enough}}
+ p = local.data(); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ (void)view; // expected-note {{later used here}}
+ (void)p; // expected-note {{later used here}}
view = kGlobal;
p = kGlobal.data();
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index 973e095fb68b4..4829c080b1230 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -539,3 +539,22 @@ void function_captured_ref_invalidated() {
}
} // namespace callable_wrappers
+
+namespace ReferenceFieldToVectorElement {
+struct S {
+ std::string& in;
+
+ S(std::vector<std::string> strings)
+ : in(strings[0]) { // expected-warning {{object whose reference is captured is later invalidated}}
+ strings.push_back("42"); // expected-note {{invalidated here}}
+ in = "use-after-invalidation"; // expected-note {{later used here}}
+ }
+
+ // FIXME: Detect invalidation of reference parameter.
+ S(std::vector<std::string>& strings, int test2)
+ : in(strings[0]) {
+ strings.push_back("42");
+ in = "use-after-invalidation";
+ }
+};
+} // namespace ReferenceFieldToVectorElement
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 082db5d5cbf15..68af1d4451f68 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2634,15 +2634,16 @@ struct Holder {
} // namespace CXXDefaultInitExprTests
namespace base_class_fields {
-struct X { int* x; }; // expected-note {{this field dangles}}
+struct X { int* x; };
struct Y : X {
int* y;
void bar() {
{
int a;
- x = &a; // expected-warning {{address of stack memory escapes to a field}}
- }
- (void)x;
+ x = &a; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)x; // expected-note {{later used here}}
+ x = nullptr;
}
};
} // namespace base_class_fields
``````````
</details>
https://github.com/llvm/llvm-project/pull/191865
More information about the cfe-commits
mailing list