[clang] 78cd6c9 - [LifetimeSafety] Detect use-after-scope through fields in member calls (#191731)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 14 00:04:09 PDT 2026
Author: NeKon69
Date: 2026-04-14T12:34:04+05:30
New Revision: 78cd6c9b28f99e201bf44c84044517820f32305e
URL: https://github.com/llvm/llvm-project/commit/78cd6c9b28f99e201bf44c84044517820f32305e
DIFF: https://github.com/llvm/llvm-project/commit/78cd6c9b28f99e201bf44c84044517820f32305e.diff
LOG: [LifetimeSafety] Detect use-after-scope through fields in member calls (#191731)
Add `UseFact`s for field origins when calling instance methods.
Fixes #182945
---------
Co-authored-by: Utkarsh Saxena <usx at google.com>
Added:
Modified:
clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
clang/test/Sema/warn-lifetime-safety-invalidations.cpp
clang/test/Sema/warn-lifetime-safety.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index cab524f12bab3..3cf5971973c2a 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -75,6 +75,10 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void handleExitBlock();
+ /// Mark all fields of the implicit object as used for an instance method
+ /// call, since the callee may access any part of the object.
+ void handleImplicitObjectFieldUses(const Expr *Call, const FunctionDecl *FD);
+
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
/// Detects arguments passed to rvalue reference parameters and creates
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2a2ef88987286..cae56ddd3d7c3 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -728,6 +728,38 @@ void FactsGenerator::handleInvalidatingCall(const Expr *Call,
ThisList->getOuterOriginID(), Call));
}
+void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call,
+ const FunctionDecl *FD) {
+ const auto *MemberCall = dyn_cast_or_null<CXXMemberCallExpr>(Call);
+ if (!MemberCall)
+ return;
+
+ if (!isa_and_present<CXXThisExpr>(
+ MemberCall->getImplicitObjectArgument()->IgnoreImpCasts()))
+ return;
+
+ const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ assert(MD && "Function must be a CXXMethodDecl for member calls");
+
+ const auto *ClassDecl = MD->getParent()->getDefinition();
+ if (!ClassDecl)
+ return;
+
+ const auto UseFields = [&](const CXXRecordDecl *RD) {
+ for (const auto *Field : RD->fields())
+ if (auto *FieldList = getOriginsList(*Field))
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<UseFact>(Call, FieldList));
+ };
+
+ UseFields(ClassDecl);
+
+ ClassDecl->forallBases([&](const CXXRecordDecl *Base) {
+ UseFields(Base);
+ return true;
+ });
+}
+
void FactsGenerator::handleFunctionCall(const Expr *Call,
const FunctionDecl *FD,
ArrayRef<const Expr *> Args,
@@ -742,6 +774,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
handleUse(Arg);
handleInvalidatingCall(Call, FD, Args);
handleMovedArgsInCall(FD, Args);
+ handleImplicitObjectFieldUses(Call, FD);
if (!CallList)
return;
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index c7f920c03736a..5a77ac97d16b1 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -512,3 +512,18 @@ void ref_capture_reassigned_to_safe() {
lambda(); // should not warn
}
} // namespace lambda_capture_invalidation
+
+namespace method_call_uses_field_invalidation {
+
+struct S {
+ std::string_view v;
+ void bar();
+ void baz(){
+ std::vector<std::string> vec = {"42"};
+ v = vec[0]; // expected-warning {{object whose reference is captured is later invalidated}}
+ vec.push_back("1"); // expected-note {{invalidated here}}
+ bar(); // expected-note {{later used here}}
+ v = nullptr;
+ }
+};
+} // namespace method_call_uses_field_invalidation
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 2aca44daeb0aa..f87b5cbdd0230 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2532,6 +2532,73 @@ int *noreturn_dead_nested(bool cond, bool cond2, int *num) {
} // namespace conditional_operator_control_flow
+namespace method_call_uses_field_origins {
+int GLOBAL_INT;
+std::string GLOBAL_STRING{"123"};
+
+struct S {
+ int* p_;
+ void bar();
+ void foo() {
+ {
+ int num;
+ this->p_ = # // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ bar(); // expected-note {{later used here}}
+ this->p_ = &GLOBAL_INT;
+ }
+ void baz() {
+ {
+ int num;
+ this->p_ = #
+ }
+ this->p_ = &GLOBAL_INT;
+ bar();
+ }
+};
+
+struct T {
+ std::string_view v;
+ void bar();
+ void foo() {
+ v = std::string("tmp"); // expected-warning {{object whose reference is captured does not live long enough}} expected-note {{destroyed here}}
+ bar(); // expected-note {{later used here}}
+ }
+};
+
+// FIXME: false-negative
+void foo() {
+ S s;
+ {
+ int num;
+ s.p_ = # // does not warn
+ }
+ s.bar();
+ s.p_ = &GLOBAL_INT;
+}
+
+struct S2 : S {
+ void bar2();
+ void foo2() {
+ {
+ int num;
+ this->p_ = # // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ bar(); // expected-note {{later used here}}
+ this->p_ = &GLOBAL_INT;
+ }
+ void baz2() {
+ {
+ int num;
+ this->p_ = # // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ bar2(); // expected-note {{later used here}}
+ this->p_ = nullptr;
+ }
+};
+
+} // namespace method_call_uses_field_origins
+
namespace CXXDefaultInitExprTests {
struct Holder {
std::string_view view = std::string("temporary"); // expected-warning {{address of stack memory escapes to a field}} expected-note {{this field dangles}}
More information about the cfe-commits
mailing list