https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/179093
>From ff755a9133d67b2959e65e3a2410b73d9b249a02 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 | 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 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
