https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/176805
>From 45af9400622db8746f960b42e96c3955136db947 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Mon, 19 Jan 2026 19:28:46 +0000 Subject: [PATCH] Detect dangling references to field members --- .../Analyses/LifetimeSafety/FactsGenerator.h | 1 + .../LifetimeSafety/FactsGenerator.cpp | 15 ++++++++++ .../Sema/warn-lifetime-analysis-nocfg.cpp | 30 +++++++++---------- .../Sema/warn-lifetime-safety-suggestions.cpp | 6 ++-- clang/test/Sema/warn-lifetime-safety.cpp | 28 +++++++++++++++++ 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index ee2f1aae4a4c5..a47505ee9f159 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -37,6 +37,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { void VisitDeclRefExpr(const DeclRefExpr *DRE); void VisitCXXConstructExpr(const CXXConstructExpr *CCE); void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE); + void VisitMemberExpr(const MemberExpr *ME); void VisitCallExpr(const CallExpr *CE); void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N); void VisitImplicitCastExpr(const ImplicitCastExpr *ICE); diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index bbab0627bdead..ccedd0522cbb3 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -203,6 +203,21 @@ void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { } } +void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) { + if (isa<FieldDecl>(ME->getMemberDecl())) { + assert(ME->isGLValue() && "Field member should be GL value"); + OriginList *Dst = getOriginsList(*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"); + // The field's glvalue (outermost origin) holds the same loans as the base + // expression. + CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( + Dst->getOuterOriginID(), Src->getOuterOriginID(), + /*Kill=*/true)); + } +} + static bool isStdMove(const FunctionDecl *FD) { return FD && FD->isInStdNamespace() && FD->getIdentifier() && FD->getName() == "move"; diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index 5b6a548389bd5..cb06ec4930907 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -1009,15 +1009,12 @@ void operator_star_arrow_of_iterators_false_positive_no_cfg_analysis() { const char* q = (*v.begin()).second.data(); const std::string& r = (*v.begin()).second; - // FIXME: Detect this using the CFG-based lifetime analysis. - // Detect dangling references to struct field. - // https://github.com/llvm/llvm-project/issues/176144 auto temporary = []() { return std::vector<std::pair<int, std::string>>{{1, "1"}}; }; - const char* x = temporary().begin()->second.data(); - const char* y = (*temporary().begin()).second.data(); - const std::string& z = (*temporary().begin()).second; + const char* x = temporary().begin()->second.data(); // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} + const char* y = (*temporary().begin()).second.data(); // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} + const std::string& z = (*temporary().begin()).second; // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} - use(p, q, r, x, y, z); + use(p, q, r, x, y, z); // cfg-note 3 {{later used here}} } } // namespace iterator_arrow @@ -1057,24 +1054,27 @@ struct S { }; struct Q { const S* get() const [[clang::lifetimebound]]; + ~Q(); }; std::string_view foo(std::string_view sv [[clang::lifetimebound]]); -// FIXME: Detect this using the CFG-based lifetime analysis. -// Detect dangling references to struct field. -// https://github.com/llvm/llvm-project/issues/176144 void test1() { std::string_view k1 = S().sv; // OK - std::string_view k2 = S().s; // expected-warning {{object backing the pointer will}} + std::string_view k2 = S().s; // expected-warning {{object backing the pointer will}} \ + // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} std::string_view k3 = Q().get()->sv; // OK - std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}} + std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}} \ + // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} - std::string_view lb1 = foo(S().s); // expected-warning {{object backing the pointer will}} - std::string_view lb2 = foo(Q().get()->s); // expected-warning {{object backing the pointer will}} - use(k1, k2, k3, k4, lb1, lb2); + std::string_view lb1 = foo(S().s); // expected-warning {{object backing the pointer will}} \ + // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} + std::string_view lb2 = foo(Q().get()->s); // expected-warning {{object backing the pointer will}} \ + // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} + + use(k1, k2, k3, k4, lb1, lb2); // cfg-note 4 {{later used here}} } struct Bar {}; diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index 6e3a6f1fd9117..bb8fd3933b55d 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -110,9 +110,9 @@ struct Container { MyObj data; const MyObj& getData() [[clang::lifetimebound]] { return data; } }; -// FIXME: c.data does not forward loans -View return_struct_field(const Container& c) { - return c.data; + +View return_struct_field(const Container& c) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + return c.data; // expected-note {{param returned here}} } View return_struct_lifetimebound_getter(Container& c) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} return c.getData().getView(); // expected-note {{param returned here}} diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 13666b0402522..84a6bc16ba52a 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -1492,3 +1492,31 @@ const std::string& test_return() { return x; // expected-note {{later used here}} } } // namespace reference_type_decl_ref_expr + +namespace field_access{ + +struct S { + std::string s; + std::string_view sv; +}; + +void uaf() { + std::string_view view; + { + S str; + S* p = &str; // expected-warning {{object whose reference is captured does not live long enough}} + view = p->s; + } // expected-note {{destroyed here}} + (void)view; // expected-note {{later used here}} +} + +void not_uaf() { + std::string_view view; + { + S str; + S* p = &str; + view = p->sv; + } + (void)view; +} +} // namespace field_access _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
