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 <[email protected]> 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 { _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
