[llvm-branch-commits] [clang] [LifetimeSafety] Detect use-after-invalidation for STL containers (PR #179093)

Utkarsh Saxena via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Feb 4 07:44:44 PST 2026


https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/179093

>From 1970c90828fde74330f2f532e860e61b5500e247 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 31 Jan 2026 21:48:00 +0000
Subject: [PATCH] use-after-invalidation

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  |  25 ++
 .../LifetimeSafety/LifetimeAnnotations.h      |   4 +
 .../Analyses/LifetimeSafety/LifetimeSafety.h  |   6 +
 .../Analysis/Analyses/LifetimeSafety/Loans.h  |   7 +-
 clang/include/clang/Basic/DiagnosticGroups.td |  10 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 +
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  75 +++-
 clang/lib/Analysis/LifetimeSafety/Dataflow.h  |   3 +
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   |   7 +
 .../LifetimeSafety/FactsGenerator.cpp         |  11 +
 .../LifetimeSafety/LifetimeAnnotations.cpp    |  72 ++++
 .../LifetimeSafety/LoanPropagation.cpp        |   1 +
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  13 +
 clang/test/Sema/Inputs/lifetime-analysis.h    |  27 +-
 .../warn-lifetime-safety-invalidations.cpp    | 320 ++++++++++++++++++
 15 files changed, 579 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/Sema/warn-lifetime-safety-invalidations.cpp

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 0eb5b248fa1f6..f9d55991f2e09 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -51,6 +51,8 @@ class Fact {
     TestPoint,
     /// An origin that escapes the function scope (e.g., via return).
     OriginEscapes,
+    /// An origin is invalidated (e.g. vector resized).
+    InvalidateOrigin,
   };
 
 private:
@@ -222,6 +224,29 @@ class UseFact : public Fact {
             const OriginManager &OM) const override;
 };
 
+/// Represents that an origin's storage has been invalidated by a container
+/// operation (e.g., vector::push_back may reallocate, invalidating iterators).
+/// Created when a container method that may invalidate references/iterators
+/// is called on the container.
+class InvalidateOriginFact : public Fact {
+  OriginID OID;
+  const Expr *InvalidationExpr;
+
+public:
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::InvalidateOrigin;
+  }
+
+  InvalidateOriginFact(OriginID OID, const Expr *InvalidationExpr)
+      : Fact(Kind::InvalidateOrigin), OID(OID),
+        InvalidationExpr(InvalidationExpr) {}
+
+  OriginID getInvalidatedOrigin() const { return OID; }
+  const Expr *getInvalidationExpr() const { return InvalidationExpr; }
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override;
+};
+
 /// Top-level origin of the expression which was found to be moved, e.g, when
 /// being used as an argument to an r-value reference parameter.
 class MovedOriginFact : public Fact {
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 3ca7a79d32ee1..47660a2e81eb6 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -66,6 +66,10 @@ bool isGslPointerType(QualType QT);
 // Tells whether the type is annotated with [[gsl::Owner]].
 bool isGslOwnerType(QualType QT);
 
+// Returns true if the given method invalidates iterators or references to
+// container elements (e.g. vector::push_back).
+bool isContainerInvalidationMethod(const CXXMethodDecl *MD);
+
 } // namespace clang::lifetimes
 
 #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 6e87951c8961d..ac1b7bcd2426a 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -74,6 +74,12 @@ class LifetimeSafetySemaHelper {
                                    const Expr *MovedExpr,
                                    SourceLocation ExpiryLoc) {}
 
+  // Reports when a reference/iterator is used after the container operation
+  // that invalidated it.
+  virtual void reportUseAfterInvalidation(const Expr *IssueExpr,
+                                          const Expr *UseExpr,
+                                          const Expr *InvalidationExpr) {}
+
   // Suggests lifetime bound annotations for function paramters.
   virtual void suggestLifetimeboundToParmVar(SuggestionScope Scope,
                                              const ParmVarDecl *ParmToAnnotate,
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index f9decf61e1f69..18c9a23027752 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -39,12 +39,17 @@ struct AccessPath {
   //   temporary object materialized via this MaterializeTemporaryExpr.
   const llvm::PointerUnion<const clang::ValueDecl *,
                            const clang::MaterializeTemporaryExpr *>
-      P;
+      P = nullptr;
 
 public:
+  AccessPath() {};
   AccessPath(const clang::ValueDecl *D) : P(D) {}
   AccessPath(const clang::MaterializeTemporaryExpr *MTE) : P(MTE) {}
 
+  operator bool() const {
+    return getAsValueDecl() || getAsMaterializeTemporaryExpr();
+  }
+
   const clang::ValueDecl *getAsValueDecl() const {
     return P.dyn_cast<const clang::ValueDecl *>();
   }
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 2f128fda5e31f..2d89b71d52d96 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -546,10 +546,18 @@ def LifetimeSafetyDanglingFieldStrict : DiagGroup<"lifetime-safety-dangling-fiel
   }];
 }
 
+
+def LifetimeSafetyInvalidation : DiagGroup<"lifetime-safety-invalidation"> {
+  code Documentation = [{
+    Warning to detect invalidation of references.
+  }];
+}
+
 def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
                                          [LifetimeSafetyDanglingField]>;
 def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
-                                    [LifetimeSafetyDanglingFieldStrict]>;
+                                    [LifetimeSafetyDanglingFieldStrict,
+                                    LifetimeSafetyInvalidation]>;
 
 def LifetimeSafety : DiagGroup<"lifetime-safety",
                                [LifetimeSafetyPermissive, LifetimeSafetyStrict]> {
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c6484295504a4..b72bdfb175b12 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10832,6 +10832,11 @@ def warn_lifetime_safety_return_stack_addr_moved_strict
       InGroup<LifetimeSafetyStrict>,
       DefaultIgnore;
 
+def warn_lifetime_safety_invalidation
+    : Warning<"object whose reference is captured is later invalidated">,
+      InGroup<LifetimeSafetyInvalidation>,
+      DefaultIgnore;
+
 def warn_lifetime_safety_dangling_field
     : Warning<"address of stack memory escapes to a field">,
       InGroup<LifetimeSafetyDanglingField>,
@@ -10844,6 +10849,7 @@ def warn_lifetime_safety_dangling_field_moved
       DefaultIgnore;
 
 def note_lifetime_safety_used_here : Note<"later used here">;
+def note_lifetime_safety_invalidated_here : Note<"invalidated here">;
 def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
 def note_lifetime_safety_returned_here : Note<"returned here">;
 def note_lifetime_safety_moved_here : Note<"potentially moved here">;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 59ae665e652a6..dbc69ba901a9b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -24,6 +24,7 @@
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/TimeProfiler.h"
 
@@ -48,6 +49,7 @@ struct PendingWarning {
   SourceLocation ExpiryLoc; // Where the loan expired.
   llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
   const Expr *MovedExpr;
+  const Expr *InvalidatedByExpr;
   Confidence ConfidenceLevel;
 };
 
@@ -80,6 +82,8 @@ class LifetimeChecker {
       for (const Fact *F : FactMgr.getFacts(B))
         if (const auto *EF = F->getAs<ExpireFact>())
           checkExpiry(EF);
+        else if (const auto *IOF = F->getAs<InvalidateOriginFact>())
+          checkInvalidation(IOF);
         else if (const auto *OEF = F->getAs<OriginEscapesFact>())
           checkAnnotations(OEF);
     issuePendingWarnings();
@@ -175,9 +179,66 @@ class LifetimeChecker {
     FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
                                      /*BestCausingFact=*/BestCausingFact,
                                      /*MovedExpr=*/MovedExpr,
+                                     /*InvalidatedByExpr=*/nullptr,
                                      /*ConfidenceLevel=*/CurConfidence};
   }
 
+  // TODO: Doc.
+  void checkInvalidation(const InvalidateOriginFact *IOF) {
+    OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
+    /// Get loans directly pointing to the invalidated container
+    LoanSet DirectlyInvalidatedLoans =
+        LoanPropagation.getLoans(InvalidatedOrigin, IOF);
+    llvm::DenseSet<LoanID> AllInvalidatedLoans;
+
+    auto GetAccessPath = [](const Loan *L) -> AccessPath {
+      if (auto *PL = dyn_cast<PathLoan>(L))
+        return PL->getAccessPath();
+      // TODO: Handle place holder loans.
+      // if (auto *PL = dyn_cast<PlaceholderLoan>(L))
+      //   return PL->getParmVarDecl();
+      return {};
+    };
+
+    // Build set of all loans that reference the invalidated container.
+    // We match loans by AccessPath because multiple IssueFacts may create
+    // loans to the same container.
+    FactMgr.forAllPredecessors(IOF, [&](const Fact *PF) {
+      auto *IF = PF->getAs<IssueFact>();
+      if (!IF)
+        return;
+      for (LoanID LID : DirectlyInvalidatedLoans) {
+        AccessPath InvalidPath =
+            GetAccessPath(FactMgr.getLoanMgr().getLoan(LID));
+        if (!InvalidPath)
+          continue;
+        LoanID ReachableLID = IF->getLoanID();
+        const Loan *ReachableLoan = FactMgr.getLoanMgr().getLoan(ReachableLID);
+        if (GetAccessPath(ReachableLoan) == InvalidPath)
+          AllInvalidatedLoans.insert(ReachableLID);
+      }
+    });
+    // For each live origin, check if it holds an invalidated loan and report.
+    LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
+    for (auto &[OID, LiveInfo] : Origins) {
+      LoanSet HeldLoans = LoanPropagation.getLoans(OID, IOF);
+      for (LoanID HeldLID : HeldLoans)
+        if (AllInvalidatedLoans.count(HeldLID)) {
+          Confidence CurConfidence = livenessKindToConfidence(LiveInfo.Kind);
+          Confidence LastConf =
+              FinalWarningsMap.lookup(HeldLID).ConfidenceLevel;
+          if (LastConf < CurConfidence) {
+            FinalWarningsMap[HeldLID] = {
+                /*ExpiryLoc=*/{},
+                /*CausingFact=*/LiveInfo.CausingFact,
+                /*MovedExpr=*/nullptr,
+                /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
+                /*ConfidenceLevel=*/CurConfidence};
+          }
+        }
+    }
+  }
+
   void issuePendingWarnings() {
     if (!SemaHelper)
       return;
@@ -191,11 +252,15 @@ class LifetimeChecker {
       const Expr *MovedExpr = Warning.MovedExpr;
       SourceLocation ExpiryLoc = Warning.ExpiryLoc;
 
-      if (const auto *UF = CausingFact.dyn_cast<const UseFact *>())
-        SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
-                                       ExpiryLoc, Confidence);
-      else if (const auto *OEF =
-                   CausingFact.dyn_cast<const OriginEscapesFact *>()) {
+      if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
+        if (Warning.InvalidatedByExpr)
+          SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
+                                                 Warning.InvalidatedByExpr);
+        else
+          SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
+                                         ExpiryLoc, Confidence);
+      } else if (const auto *OEF =
+                     CausingFact.dyn_cast<const OriginEscapesFact *>()) {
         if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
           SemaHelper->reportUseAfterReturn(IssueExpr,
                                            RetEscape->getReturnExpr(),
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 4de9bbb54af9f..0f64ac8a36ef7 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -178,6 +178,8 @@ class DataflowAnalysis {
       return D->transfer(In, *F->getAs<UseFact>());
     case Fact::Kind::TestPoint:
       return D->transfer(In, *F->getAs<TestPointFact>());
+    case Fact::Kind::InvalidateOrigin:
+      return D->transfer(In, *F->getAs<InvalidateOriginFact>());
     }
     llvm_unreachable("Unknown fact kind");
   }
@@ -190,6 +192,7 @@ class DataflowAnalysis {
   Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; }
   Lattice transfer(Lattice In, const UseFact &) { return In; }
   Lattice transfer(Lattice In, const TestPointFact &) { return In; }
+  Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; }
 };
 } // namespace clang::lifetimes::internal
 #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index e782d6de82ea6..c963d9c45fa9d 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -82,6 +82,13 @@ void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
   OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
 }
 
+void InvalidateOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                                const OriginManager &OM) const {
+  OS << "InvalidateOrigin (";
+  OM.dump(getInvalidatedOrigin(), OS);
+  OS << ")\n";
+}
+
 void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
                          const OriginManager &) const {
   OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index bdb48b0c81172..6e2a8f94e1d80 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -551,6 +551,17 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
   FD = getDeclWithMergedLifetimeBoundAttrs(FD);
   if (!FD)
     return;
+
+  // Detect container methods that invalidate iterators/references.
+  // For instance methods, Args[0] is the implicit 'this' pointer.
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+      MD && MD->isInstance() && isContainerInvalidationMethod(MD)) {
+    OriginList *ThisList = getOriginsList(*Args[0]);
+    if (ThisList)
+      CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>(
+          ThisList->getOuterOriginID(), Call));
+  }
+
   handleMovedArgsInCall(FD, Args);
   if (!CallList)
     return;
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 385fb2f05ae2a..2719f1fedb6f3 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -255,4 +255,76 @@ template <typename T> static bool isRecordWithAttr(QualType Type) {
 bool isGslPointerType(QualType QT) { return isRecordWithAttr<PointerAttr>(QT); }
 bool isGslOwnerType(QualType QT) { return isRecordWithAttr<OwnerAttr>(QT); }
 
+bool isContainerInvalidationMethod(const CXXMethodDecl *MD) {
+  if (!MD)
+    return false;
+  const CXXRecordDecl *RD = MD->getParent();
+  if (!RD || !isInStlNamespace(RD))
+    return false;
+
+  StringRef ContainerName;
+  if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+    ContainerName = CTSD->getSpecializedTemplate()->getName();
+  else if (RD->getIdentifier())
+    ContainerName = RD->getName();
+  else
+    return false;
+
+  static llvm::StringSet<> Containers = {
+      // Sequence
+      "vector", "basic_string", "deque", "list", "forward_list",
+      // Adaptors
+      "stack", "priority_queue", "queue",
+      // Associative
+      "set", "multiset", "map", "multimap",
+      // Unordered Associative
+      "unordered_set", "unordered_multiset", "unordered_map",
+      "unordered_multimap",
+      // C++23 Flat
+      "flat_map", "flat_set", "flat_multimap", "flat_multiset"};
+
+  if (!Containers.contains(ContainerName))
+    return false;
+
+  // Handle Operators via OverloadedOperatorKind
+  OverloadedOperatorKind OO = MD->getOverloadedOperator();
+  if (OO != OO_None) {
+    switch (OO) {
+    case OO_Equal:     // operator= : Always invalidates (Assignment)
+    case OO_PlusEqual: // operator+= : Append (String/Vector)
+      return true;
+    case OO_Subscript: // operator[] : Invalidation only for Maps
+                       // (Insert-or-access)
+    {
+      static llvm::StringSet<> MapContainers = {"map", "unordered_map",
+                                                "flat_map"};
+      return MapContainers.contains(ContainerName);
+    }
+    default:
+      return false;
+    }
+  }
+
+  if (!MD->getIdentifier())
+    return false;
+  static const llvm::StringSet<> InvalidatingMembers = {
+      // Basic Insertion/Emplacement
+      "push_front", "push_back", "emplace_front", "emplace_back", "insert",
+      "emplace", "push",
+      // Basic Removal/Clearing
+      "pop_front", "pop_back", "pop", "erase", "clear",
+      // Memory Management
+      "reserve", "resize", "shrink_to_fit",
+      // Assignment (Named)
+      "assign", "swap",
+      // String Specifics
+      "append", "replace",
+      // Forward List Specifics
+      "insert_after", "emplace_after", "erase_after",
+      // List/Forward_list Splicing & Merging
+      "splice", "splice_after", "merge", "remove", "remove_if", "unique",
+      // Modern C++ (C++17/23)
+      "extract", "try_emplace", "insert_range", "append_range", "assign_range"};
+  return InvalidatingMembers.contains(MD->getName());
+}
 } // namespace clang::lifetimes
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index 0996d11f0cdeb..8a020eb829be6 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -67,6 +67,7 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
       case Fact::Kind::OriginEscapes:
       case Fact::Kind::Expire:
       case Fact::Kind::TestPoint:
+      case Fact::Kind::InvalidateOrigin:
         break;
       }
     }
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 858d5b7f67b58..a31744d4c6115 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2925,6 +2925,17 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
         << DanglingField->getEndLoc();
   }
 
+  void reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr,
+                                  const Expr *InvalidationExpr) override {
+    S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_invalidation)
+        << IssueExpr->getSourceRange();
+    S.Diag(InvalidationExpr->getExprLoc(),
+           diag::note_lifetime_safety_invalidated_here)
+        << InvalidationExpr->getSourceRange();
+    S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
+        << UseExpr->getSourceRange();
+  }
+
   void suggestLifetimeboundToParmVar(SuggestionScope Scope,
                                      const ParmVarDecl *ParmToAnnotate,
                                      const Expr *EscapeExpr) override {
@@ -3138,6 +3149,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
       !Diags.isIgnored(
           diag::warn_lifetime_safety_return_stack_addr_moved_strict,
           D->getBeginLoc()) ||
+      !Diags.isIgnored(diag::warn_lifetime_safety_invalidation,
+                       D->getBeginLoc()) ||
       !Diags.isIgnored(diag::warn_lifetime_safety_noescape_escapes,
                        D->getBeginLoc());
   bool EnableLifetimeSafetyAnalysis =
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 987fb012d59c0..38a28e8dcc49c 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -7,6 +7,8 @@ struct basic_iterator {
   T* operator->() const;
 };
 
+template<typename T>
+bool operator==(basic_iterator<T>, basic_iterator<T>);
 template<typename T>
 bool operator!=(basic_iterator<T>, basic_iterator<T>);
 }
@@ -19,6 +21,10 @@ template<typename T> struct remove_reference<T &&> { typedef T type; };
 template< class InputIt, class T >
 InputIt find( InputIt first, InputIt last, const T& value );
 
+template< class ForwardIt1, class ForwardIt2 >
+ForwardIt1 search( ForwardIt1 first, ForwardIt1 last,
+                   ForwardIt2 s_first, ForwardIt2 s_last );
+
 template<typename T>
 typename remove_reference<T>::type &&move(T &&t) noexcept;
 
@@ -27,6 +33,8 @@ auto data(const C &c) -> decltype(c.data());
 
 template <typename C>
 auto begin(C &c) -> decltype(c.begin());
+template <typename C>
+auto end(C &c) -> decltype(c.end());
 
 template<typename T, int N>
 T *begin(T (&array)[N]);
@@ -52,15 +60,29 @@ struct vector {
   template<typename InputIterator>
 	vector(InputIterator first, InputIterator __last);
 
+  T& operator[](unsigned);
+
   T &  at(int n) &;
   T && at(int n) &&;
 
   void push_back(const T&);
   void push_back(T&&);
   const T& back() const;
-  void insert(iterator, T&&);
+  void pop_back();
+  iterator insert(iterator, T&&);
+  void resize(size_t);
+  void erase(iterator);
+  void clear();
 };
 
+template<class Key,class T>
+struct unordered_map {
+  T& operator[](const Key& key);
+};
+
+template<class T>
+void swap( T& a, T& b );
+
 template<typename A, typename B>
 struct pair {
   A first;
@@ -92,6 +114,9 @@ struct basic_string {
   basic_string(basic_string<T> &&);
   basic_string(const T *);
   ~basic_string();
+  basic_string& operator=(const basic_string&);
+  basic_string& operator+=(const basic_string&);
+  basic_string& operator+=(const T*);
   const T *c_str() const;
   operator basic_string_view<T> () const;
   using const_iterator = iter<T>;
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
new file mode 100644
index 0000000000000..8a0c6fdabb7ed
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -0,0 +1,320 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety-invalidation -Wno-dangling -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+namespace logging {
+class VoidifyStream {
+ public:
+  VoidifyStream() = default;
+  template <typename T>
+  void operator&(const T&) {}
+};
+class CheckError {
+ public:
+  static CheckError Check(const char* file, int line, const char* condition);
+};
+}  // namespace logging
+#define LAZY_CHECK_STREAM(stream, condition) \
+  !(condition) ? (void)0 : ::logging::VoidifyStream() & (stream)
+#define CHECK(condition)                                                     \
+  LAZY_CHECK_STREAM(                                                         \
+      ::logging::CheckError::Check(__FILE__, __LINE__, #condition).stream(), \
+      !(condition))
+
+bool Bool();
+void PassBool(bool);
+
+// --- Test Cases ---
+
+namespace SimpleResize {
+void IteratorInvalidAfterResize(int new_size) {
+  std::vector<int> v;
+  auto it = std::begin(v);  // expected-warning {{object whose reference is captured is later invalidated}}
+  v.resize(new_size);       // expected-note {{invalidated here}}
+  *it;                      // expected-note {{later used here}}
+}
+
+void IteratorValidAfterResize(int new_size) {
+  std::vector<int> v;
+  auto it = std::begin(v);
+  v.resize(new_size);
+  it = std::begin(v);
+  if (it != std::end(v)) {
+    *it;  // ok
+  }
+}
+}  // namespace SimpleResize
+
+namespace CheckModel {
+void IteratorValidAfterCheck() {
+  std::vector<int> v;
+  auto it = v.begin();
+  *it;  // ok
+}
+}  // namespace CheckModel
+
+namespace PointerToContainer {
+std::vector<int>* GetContainerPointer();
+void PointerToContainerTest() {
+  // FIXME: Use opaque loans.
+  std::vector<int>* v = GetContainerPointer();
+  auto it = v->begin();
+  *it = 0;  // not-ok
+}
+void PointerToContainerTest(std::vector<int>* v) {
+  // FIXME: Handle placeholder loans.
+  auto it = v->begin();
+  *it = 0;  // not-ok
+}
+}  // namespace PointerToContainer
+
+namespace InvalidateBeforeSwap {
+void InvalidateBeforeSwapIterators(std::vector<int> v1, std::vector<int> v2) {
+  auto it1 = std::begin(v1); // expected-warning {{object whose reference is captured is later invalidated}}
+  auto it2 = std::begin(v2);
+  if (it1 == std::end(v1) || it2 == std::end(v2)) return;
+  *it1 = 0;     // ok
+  *it2 = 0;     // ok
+  v1.clear();   // expected-note {{invalidated here}}
+  *it1 = 0;     // expected-note {{later used here}}
+  // FIXME: Handle invalidating functions like std::swap.
+  std::swap(it1, it2);
+  *it1 = 0;  // ok
+  *it2 = 0;  // not-ok
+}
+
+void InvalidateBeforeSwapContainers(std::vector<int> v1, std::vector<int> v2) {
+  auto it1 = std::begin(v1);  // expected-warning {{object whose reference is captured is later invalidated}}
+  auto it2 = std::begin(v2);
+  if (it1 == std::end(v1) || it2 == std::end(v2)) return;
+  *it1 = 0;     // ok
+  *it2 = 0;     // ok
+  v1.clear();   // expected-note {{invalidated here}}
+  *it1 = 0;     // expected-note {{later used here}}
+}
+}  // namespace InvalidateBeforeSwap
+
+namespace MergeConditionBasic {
+bool A();
+bool B();
+void SameConditionInvalidatesThenValidatesIterator() {
+  std::vector<int> container;
+  auto it = container.begin(); // expected-warning {{object whose reference is captured is later invalidated}}
+  if (it == container.end()) return;
+  const bool a = A();
+  if (a) {
+    container.clear();  // expected-note {{invalidated here}}
+  }
+  if (a) {
+    it = container.begin();
+    if (it == std::end(container)) return;
+  }
+  *it = 10;  // expected-note {{later used here}}
+}
+}  // namespace MergeConditionBasic
+
+namespace IteratorWithMultipleContainers {
+void MergeWithDifferentContainerValuesIteratorNotInvalidated() {
+  std::vector<int> v1, v2, v3;
+  auto it = std::find(v1.begin(), v1.end(), 10);
+  if (Bool()) {
+    it = std::find(v2.begin(), v2.end(), 10);
+  } else {
+    it = std::find(v3.begin(), v3.end(), 10);
+  }
+  v1.clear();
+  *it = 20;
+}
+
+void MergeWithDifferentContainerValuesInvalidated() {
+  std::vector<int> v1, v2, v3;
+  auto it = std::find(v1.begin(), v1.end(), 10);
+  if (Bool()) {
+    it = std::find(v2.begin(), v2.end(), 10);  // expected-warning {{object whose reference is captured is later invalidated}}
+  } else {
+    it = std::find(v3.begin(), v3.end(), 10);
+  }
+  v2.clear();   // expected-note {{invalidated here}}
+  *it = 20;     // expected-note {{later used here}}
+}
+}  // namespace IteratorWithMultipleContainers
+
+namespace InvalidationInLoops {
+void IteratorInvalidationInAForLoop(std::vector<int> v) {
+  for (auto it = std::begin(v);  // expected-warning {{object whose reference is captured is later invalidated}}
+       it != std::end(v);
+       ++it) {  // expected-note {{later used here}}
+    if (Bool()) {
+      v.erase(it);  // expected-note {{invalidated here}}
+    }
+  }
+}
+
+void IteratorInvalidationInAWhileLoop(std::vector<int> v) {
+  auto it = std::begin(v);  // expected-warning {{object whose reference is captured is later invalidated}}
+  while (it != std::end(v)) {
+    if (Bool()) {
+      v.erase(it);  // expected-note {{invalidated here}}
+    }
+    ++it; // expected-note {{later used here}}
+  }
+}
+
+void IteratorInvalidationInAForeachLoop(std::vector<int> v) {
+  for (int& x : v) { // expected-warning {{object whose reference is captured is later invalidated}} \
+                     // expected-note {{later used here}}
+    if (x % 2 == 0) {
+      v.erase(std::find(v.begin(), v.end(), 1)); // expected-note {{invalidated here}}
+    }
+  }
+}
+}  // namespace InvalidationInLoops
+
+namespace StdVectorPopBack {
+void StdVectorPopBackInvalid(std::vector<int> v) {
+  auto it = v.begin();  // expected-warning {{object whose reference is captured is later invalidated}}
+  if (it == v.end()) return;
+  *it;  // ok
+  v.pop_back(); // expected-note {{invalidated here}}
+  *it;          // expected-note {{later used here}}
+}
+}  // namespace StdVectorPopBack
+
+
+namespace SimpleStdFind {
+void IteratorCheckedAfterFind(std::vector<int> v) {
+  auto it = std::find(std::begin(v), std::end(v), 3);
+  if (it != std::end(v)) {
+    *it;  // ok
+  }
+}
+
+void IteratorCheckedAfterFindThenErased(std::vector<int> v) {
+  auto it = std::find(std::begin(v), std::end(v), 3); // expected-warning {{object whose reference is captured is later invalidated}}
+  if (it != std::end(v)) {
+    v.erase(it); // expected-note {{invalidated here}}
+  }
+  *it;  // expected-note {{later used here}}
+}
+}  // namespace SimpleStdFind
+
+namespace SimpleInsert {
+void UseReturnedIteratorAfterInsert(std::vector<int> v) {
+  auto it = std::begin(v);
+  it = v.insert(it, 10);
+  if (it != std::end(v)) {
+    *it;  // ok
+  }
+}
+
+void UseInvalidIteratorAfterInsert(std::vector<int> v) {
+  auto it = std::begin(v);  // expected-warning {{object whose reference is captured is later invalidated}}
+  v.insert(it, 10);         // expected-note {{invalidated here}}
+  if (it != std::end(v)) {  // not-ok // expected-note {{later used here}}
+    *it;
+  }
+}
+}  // namespace SimpleInsert
+
+namespace SimpleStdInsert {
+void IteratorValidAfterInsert(std::vector<int> v) {
+  auto it = std::begin(v);
+  v.insert(it, 0);
+  it = std::begin(v);
+  if (it != std::end(v)) {
+    *it;  // ok
+  }
+}
+
+void IteratorInvalidAfterInsert(std::vector<int> v, int value) {
+  auto it = std::begin(v);  // expected-warning {{object whose reference is captured is later invalidated}}
+  v.insert(it, 0);          // expected-note {{invalidated here}}
+  *it;                      // expected-note {{later used here}}
+}
+}  // namespace SimpleStdInsert
+
+namespace SimpleInvalidIterators {
+void IteratorUsedAfterErase(std::vector<int> v) {
+  auto it = std::begin(v);          // expected-warning {{object whose reference is captured is later invalidated}}
+  for (; it != std::end(v); ++it) { // expected-note {{later used here}}
+    if (*it > 3) {
+      v.erase(it);                  // expected-note {{invalidated here}}
+    }
+  }
+}
+
+void IteratorUsedAfterPushBack(std::vector<int> v) {
+  auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
+  if (it != std::end(v) && *it == 3) {
+    v.push_back(4); // expected-note {{invalidated here}}
+  }
+  ++it;             // expected-note {{later used here}}
+}
+}  // namespace SimpleInvalidIterators
+
+namespace ElementReferences {
+// Testing raw pointers and references to elements, not just iterators.
+
+void ReferenceToVectorElement() {
+  std::vector<int> v = {1, 2, 3};
+  int& ref = v[0];
+  v.push_back(4);
+  // FIXME: Detect this as a use of 'ref. (<file bug>)
+  ref = 10;
+  (void)ref;
+}
+
+void PointerToVectorElement() {
+  std::vector<int> v = {1, 2, 3};
+  int* ptr = &v[0];  // expected-warning {{object whose reference is captured is later invalidated}}
+  v.resize(100);     // expected-note {{invalidated here}}
+  *ptr = 10;         // expected-note {{later used here}}
+}
+
+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.
+}
+} // namespace ElementReferences
+
+namespace Strings {
+
+void append(std::string str) {
+  std::string_view view = str;  // expected-warning {{object whose reference is captured is later invalidated}}
+  str += "456";                 // expected-note {{invalidated here}}
+  (void)view;                   // expected-note {{later used here}}
+}
+void reassign(std::string str, std::string str2) {
+  std::string_view view = str;  // expected-warning {{object whose reference is captured is later invalidated}}
+  str = str2;                   // expected-note {{invalidated here}}
+  (void)view;                   // expected-note {{later used here}}
+}
+} // namespace Strings
+
+namespace ContainersAsFields {
+struct S {
+  std::vector<std::string> strings1;
+  std::vector<std::string> strings2;
+};
+// FIXME: Make Paths more precise to reason at field granularity.
+//        Here, we invalidate paths `s.strings2` and deeper but sibling paths
+void Invalidate1Use2() {
+    S s;
+    auto it = s.strings1.begin();  // expected-warning {{object whose reference is captured is later invalidated}}
+    s.strings2.push_back("1");     // expected-note {{invalidated here}}
+    *it;                           // expected-note {{later used here}}
+}
+void Invalidate1UseS() {
+    S s;
+    S* p = &s;                 // expected-warning {{object whose reference is captured is later invalidated}}
+    s.strings2.push_back("1"); // expected-note {{invalidated here}}
+    (void)*p;                  // expected-note {{later used here}}
+}
+void ContainerAsPointer() {
+    std::vector<std::string> s;                
+    std::vector<std::string>* p = &s; // expected-warning {{object whose reference is captured is later invalidated}}
+    p->push_back("1");                // expected-note {{invalidated here}}
+    (void)*p;                         // expected-note {{later used here}}
+}
+} // namespace ContainersAsFields



More information about the llvm-branch-commits mailing list