[llvm-branch-commits] [clang] use-after-invalidation (PR #179093)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Feb 1 02:01:12 PST 2026
https://github.com/usx95 created https://github.com/llvm/llvm-project/pull/179093
None
>From 7d661e812adfc2359b0878f6e4279bfd95e43ebd 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 | 21 +++++++
.../LifetimeSafety/LifetimeAnnotations.h | 4 ++
.../Analyses/LifetimeSafety/LifetimeSafety.h | 4 ++
clang/include/clang/Basic/DiagnosticGroups.td | 10 ++-
.../clang/Basic/DiagnosticSemaKinds.td | 6 ++
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 62 +++++++++++++++++--
clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 +
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 7 +++
.../LifetimeSafety/FactsGenerator.cpp | 10 +++
.../LifetimeSafety/LifetimeAnnotations.cpp | 36 +++++++++++
.../LifetimeSafety/LoanPropagation.cpp | 1 +
clang/lib/Sema/AnalysisBasedWarnings.cpp | 11 ++++
12 files changed, 169 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 14ff37cf296b8..43f1f065a6605 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,25 @@ class UseFact : public Fact {
const OriginManager &OM) const override;
};
+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..5cc562a482bb0 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.
+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..3311c592b21ee 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -74,6 +74,10 @@ class LifetimeSafetySemaHelper {
const Expr *MovedExpr,
SourceLocation ExpiryLoc) {}
+ 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/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 2f128fda5e31f..270b451344092 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -546,8 +546,16 @@ 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]>;
+ [LifetimeSafetyDanglingField,
+ LifetimeSafetyInvalidation]>;
def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
[LifetimeSafetyDanglingFieldStrict]>;
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..b73ce8ef619d2 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,53 @@ class LifetimeChecker {
FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
/*BestCausingFact=*/BestCausingFact,
/*MovedExpr=*/MovedExpr,
+ /*InvalidatedByExpr=*/nullptr,
/*ConfidenceLevel=*/CurConfidence};
}
+ void checkInvalidation(const InvalidateOriginFact *IOF) {
+ OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
+ LoanSet DirectlyInvalidatedLoans =
+ LoanPropagation.getLoans(InvalidatedOrigin, IOF);
+ llvm::DenseSet<LoanID> AllInvalidatedLoans;
+
+ FactMgr.forAllPredecessors(IOF, [&](const Fact *PF) {
+ auto *IF = PF->getAs<IssueFact>();
+ if (!IF)
+ return;
+ for (LoanID LID : DirectlyInvalidatedLoans) {
+ const Loan *InvalidatedLoan = FactMgr.getLoanMgr().getLoan(LID);
+ auto *PL = dyn_cast<PathLoan>(InvalidatedLoan);
+ if (!PL)
+ continue;
+ LoanID ReachableLID = IF->getLoanID();
+ const Loan *ReachableLoan = FactMgr.getLoanMgr().getLoan(ReachableLID);
+ if (auto *RL = dyn_cast<PathLoan>(ReachableLoan))
+ if (RL->getAccessPath() == PL->getAccessPath())
+ AllInvalidatedLoans.insert(ReachableLID);
+ }
+ });
+ 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 +239,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 7b22634a51947..7be9eddddabe9 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..f9018faeb125d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -551,6 +551,16 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
if (!FD)
return;
+
+ 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 be33caf327802..0c8ab72e54a02 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -227,4 +227,40 @@ 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;
+ }
+
+ bool IsSequenceContainer = ContainerName == "vector" ||
+ ContainerName == "basic_string" ||
+ ContainerName == "deque";
+ bool IsAssociativeContainer =
+ ContainerName == "set" || ContainerName == "multiset" ||
+ ContainerName == "map" || ContainerName == "multimap" ||
+ ContainerName == "unordered_set" || ContainerName == "unordered_multiset" ||
+ ContainerName == "unordered_map" || ContainerName == "unordered_multimap";
+
+ if (!IsSequenceContainer && !IsAssociativeContainer)
+ return false;
+
+ static const llvm::StringSet<> InvalidatingMembers = {
+ "push_back", "emplace_back", "insert", "erase", "resize",
+ "clear", "emplace", "pop_back", "swap"};
+
+ return MD->getIdentifier() && 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..d2681dd3ca6f6 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 {
More information about the llvm-branch-commits
mailing list