[clang] 47d420a - [LifetimeSafety] Add UseFacts for function arguments and assignment RHS (#180446)

via cfe-commits cfe-commits at lists.llvm.org
Fri Feb 20 10:55:08 PST 2026


Author: Utkarsh Saxena
Date: 2026-02-20T18:55:02Z
New Revision: 47d420a2c8e0f9edbdf58ce8b8dcc7d6743b4191

URL: https://github.com/llvm/llvm-project/commit/47d420a2c8e0f9edbdf58ce8b8dcc7d6743b4191
DIFF: https://github.com/llvm/llvm-project/commit/47d420a2c8e0f9edbdf58ce8b8dcc7d6743b4191.diff

LOG: [LifetimeSafety] Add UseFacts for function arguments and assignment RHS (#180446)

Add missing `UseFact` for binary operators and function call arguments
to track object usage.

These changes allow the analyzer to properly detect cases where an
object is used after being invalidated, particularly in container
operations like map access.

**Pointer vs Iterator Invalidation:**
Different containers provide different stability guarantees:
- **Pointer/Reference Stability**: Containers like `std::unordered_map`
guarantee that pointers and references to elements remain valid even
after insertions. This makes operations like `mp[2] = mp[1]` safe in
practice.
- **No Pointer Stability**: Containers like `std::flat_hash_map` (C++23)
do not provide pointer stability on insertion, making such operations
unsafe.
- **Iterator Stability**: Most containers (including
`std::unordered_map`) do not provide iterator stability. Operations that
invalidate iterators (like `erase`) make subsequent use of those
iterators unsafe.

The current implementation conservatively warns on all such cases.

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/lib/Sema/SemaAttr.cpp
    clang/test/Sema/Inputs/lifetime-analysis.h
    clang/test/Sema/warn-lifetime-safety-invalidations.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 5f4ddcb39f84c..dbe5a1eeb498e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -104,10 +104,10 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   /// If so, creates a `TestPointFact` and returns true.
   bool handleTestPoint(const CXXFunctionalCastExpr *FCE);
 
-  // A DeclRefExpr will be treated as a use of the referenced decl. It will be
+  // Treats an expression as a use of the referenced object. It will be
   // checked for use-after-free unless it is later marked as being written to
-  // (e.g. on the left-hand side of an assignment).
-  void handleUse(const DeclRefExpr *DRE);
+  // (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);
 
@@ -124,7 +124,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use
   // corresponding to the left-hand side is updated to be a "write", thereby
   // exempting it from the check.
-  llvm::DenseMap<const DeclRefExpr *, UseFact *> UseFacts;
+  llvm::DenseMap<const Expr *, UseFact *> UseFacts;
 };
 
 } // namespace clang::lifetimes::internal

diff  --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ca89e5ff17305..f39d677758393 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -359,6 +359,7 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
   // result should have the same loans as the pointer operand.
   if (BO->isCompoundAssignmentOp())
     return;
+  handleUse(BO->getRHS());
   if (BO->isAssignmentOp())
     handleAssignment(BO->getLHS(), BO->getRHS());
   // TODO: Handle assignments involving dereference like `*p = q`.
@@ -571,7 +572,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
   FD = getDeclWithMergedLifetimeBoundAttrs(FD);
   if (!FD)
     return;
-
+  // All arguments to a function are a use of the corresponding expressions.
+  for (const Expr *Arg : Args)
+    handleUse(Arg);
   handleInvalidatingCall(Call, FD, Args);
   handleMovedArgsInCall(FD, Args);
   if (!CallList)
@@ -666,24 +669,24 @@ bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) {
   return false;
 }
 
-// A DeclRefExpr will be treated as a use of the referenced decl. It will be
-// checked for use-after-free unless it is later marked as being written to
-// (e.g. on the left-hand side of an assignment).
-void FactsGenerator::handleUse(const DeclRefExpr *DRE) {
-  OriginList *List = getOriginsList(*DRE);
+void FactsGenerator::handleUse(const Expr *E) {
+  OriginList *List = getOriginsList(*E);
   if (!List)
     return;
-  // Remove the outer layer of origin which borrows from the decl directly
-  // (e.g., when this is not a reference). This is a use of the underlying decl.
-  if (!DRE->getDecl()->getType()->isReferenceType())
+  // For DeclRefExpr: Remove the outer layer of origin which borrows from the
+  // decl directly (e.g., when this is not a reference). This is a use of the
+  // underlying decl.
+  if (auto *DRE = dyn_cast<DeclRefExpr>(E);
+      DRE && !DRE->getDecl()->getType()->isReferenceType())
     List = getRValueOrigins(DRE, List);
   // Skip if there is no inner origin (e.g., when it is not a pointer type).
   if (!List)
     return;
-  UseFact *UF = FactMgr.createFact<UseFact>(DRE, List);
-  CurrentBlockFacts.push_back(UF);
-  assert(!UseFacts.contains(DRE));
-  UseFacts[DRE] = UF;
+  if (!UseFacts.contains(E)) {
+    UseFact *UF = FactMgr.createFact<UseFact>(E, List);
+    CurrentBlockFacts.push_back(UF);
+    UseFacts[E] = UF;
+  }
 }
 
 void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {

diff  --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 494cf68db8ee6..55111ca6a7cfe 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -136,6 +136,8 @@ void Sema::inferGslPointerAttribute(NamedDecl *ND,
       "unordered_map",
       "unordered_multiset",
       "unordered_multimap",
+      "flat_map",
+      "flat_set",
   };
 
   static const llvm::StringSet<> Iterators{"iterator", "const_iterator",
@@ -189,6 +191,8 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) {
       "unordered_multiset",
       "unordered_multimap",
       "variant",
+      "flat_map",
+      "flat_set",
   };
   static const llvm::StringSet<> StdPointers{
       "basic_string_view",

diff  --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 5946cee49b5dc..eaee12433342e 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -84,12 +84,23 @@ struct pair {
   B second;
 };
 
+template<class Key,class T>
+struct flat_map {
+  using iterator = __gnu_cxx::basic_iterator<std::pair<const Key, T>>;
+  T& operator[](const Key& key);
+  iterator begin();
+  iterator end();
+  iterator find(const Key& key);
+  iterator erase(iterator);
+};
+
 template<class Key,class T>
 struct unordered_map {
   using iterator = __gnu_cxx::basic_iterator<std::pair<const Key, T>>;
   T& operator[](const Key& key);
   iterator begin();
   iterator end();
+  iterator find(const Key& key);
   iterator erase(iterator);
 };
 

diff  --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index cc48b3d034468..65d676cbe8361 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -275,9 +275,26 @@ void PointerToVectorElement() {
 }
 
 void SelfInvalidatingMap() {
-  std::unordered_map<int, int> mp;
-  mp[1] = 1;
-  mp[2] = mp[1];  // FIXME: Detect this. We are mising a UseFact for the assignment params.
+  std::flat_map<int, std::string> mp;
+  // TODO: We do not have a way to 
diff erentiate between pointer stability and iterator stability!
+  //
+  // std::unordered_map and other node-based containers provide pointer/reference stability.
+  // Therefore the following is safe in practice.
+  // On the other hand, std::flat_map (since C++23) does not provide pointer stability on
+  // insertion and following is unsafe for this container.
+  mp[1] = "42";
+  mp[2]     // expected-note {{invalidated here}}
+    = 
+    mp[1];  // expected-warning {{object whose reference is captured is later invalidated}} expected-note {{later used here}}
+}
+
+void InvalidateErase() {
+  std::flat_map<int, std::string> mp;
+  // None of these containers provide iterator stability. So following is unsafe:
+  auto it = mp.find(3); // expected-warning {{object whose reference is captured is later invalidated}}
+  mp.erase(mp.find(4)); // expected-note {{invalidated here}} 
+  if (it != mp.end())   // expected-note {{later used here}}
+    *it;
 }
 } // namespace ElementReferences
 


        


More information about the cfe-commits mailing list