https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/180446
>From 93740119d642625fffa7b7b0d457158e76db0b5b Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sun, 8 Feb 2026 22:29:52 +0000 Subject: [PATCH] Improve liveness to detect more invaldiations --- .../Analyses/LifetimeSafety/FactsGenerator.h | 8 ++--- .../LifetimeSafety/FactsGenerator.cpp | 29 ++++++++++--------- clang/lib/Sema/SemaAttr.cpp | 4 +++ clang/test/Sema/Inputs/lifetime-analysis.h | 11 +++++++ .../warn-lifetime-safety-invalidations.cpp | 23 +++++++++++++-- 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index fb7d5ad91db79..43551e48c8ac0 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -102,10 +102,10 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { /// If so, creates a `TestPointFact` and returns true. bool handleTestPoint(const CXXFunctionalCastExpr *FCE); - // A DeclRefExpr will be treated as a use of the referenced decl. It will be + // Treats an expression as a use of the referenced object. It will be // checked for use-after-free unless it is later marked as being written to - // (e.g. on the left-hand side of an assignment). - void handleUse(const DeclRefExpr *DRE); + // (e.g. on the left-hand side of an assignment in the case of a DeclRefExpr). + void handleUse(const Expr *E); void markUseAsWrite(const DeclRefExpr *DRE); @@ -122,7 +122,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use // corresponding to the left-hand side is updated to be a "write", thereby // exempting it from the check. - llvm::DenseMap<const DeclRefExpr *, UseFact *> UseFacts; + llvm::DenseMap<const Expr *, UseFact *> UseFacts; }; } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 0b6e70212003c..df3ed3c4e8105 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -366,6 +366,7 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { // result should have the same loans as the pointer operand. if (BO->isCompoundAssignmentOp()) return; + handleUse(BO->getRHS()); if (BO->isAssignmentOp()) handleAssignment(BO->getLHS(), BO->getRHS()); // TODO: Handle assignments involving dereference like `*p = q`. @@ -583,7 +584,9 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, FD = getDeclWithMergedLifetimeBoundAttrs(FD); if (!FD) return; - + // All arguments to a function are a use of the corresponding expressions. + for (const Expr *Arg : Args) + handleUse(Arg); handleInvalidatingCall(Call, FD, Args); handleMovedArgsInCall(FD, Args); if (!CallList) @@ -678,24 +681,24 @@ bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) { return false; } -// A DeclRefExpr will be treated as a use of the referenced decl. It will be -// checked for use-after-free unless it is later marked as being written to -// (e.g. on the left-hand side of an assignment). -void FactsGenerator::handleUse(const DeclRefExpr *DRE) { - OriginList *List = getOriginsList(*DRE); +void FactsGenerator::handleUse(const Expr *E) { + OriginList *List = getOriginsList(*E); if (!List) return; - // Remove the outer layer of origin which borrows from the decl directly - // (e.g., when this is not a reference). This is a use of the underlying decl. - if (!DRE->getDecl()->getType()->isReferenceType()) + // For DeclRefExpr: Remove the outer layer of origin which borrows from the + // decl directly (e.g., when this is not a reference). This is a use of the + // underlying decl. + if (auto *DRE = dyn_cast<DeclRefExpr>(E); + DRE && !DRE->getDecl()->getType()->isReferenceType()) List = getRValueOrigins(DRE, List); // Skip if there is no inner origin (e.g., when it is not a pointer type). if (!List) return; - UseFact *UF = FactMgr.createFact<UseFact>(DRE, List); - CurrentBlockFacts.push_back(UF); - assert(!UseFacts.contains(DRE)); - UseFacts[DRE] = UF; + if (!UseFacts.contains(E)) { + UseFact *UF = FactMgr.createFact<UseFact>(E, List); + CurrentBlockFacts.push_back(UF); + UseFacts[E] = UF; + } } void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) { diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp index 494cf68db8ee6..55111ca6a7cfe 100644 --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -136,6 +136,8 @@ void Sema::inferGslPointerAttribute(NamedDecl *ND, "unordered_map", "unordered_multiset", "unordered_multimap", + "flat_map", + "flat_set", }; static const llvm::StringSet<> Iterators{"iterator", "const_iterator", @@ -189,6 +191,8 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) { "unordered_multiset", "unordered_multimap", "variant", + "flat_map", + "flat_set", }; static const llvm::StringSet<> StdPointers{ "basic_string_view", diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h index 5946cee49b5dc..eaee12433342e 100644 --- a/clang/test/Sema/Inputs/lifetime-analysis.h +++ b/clang/test/Sema/Inputs/lifetime-analysis.h @@ -84,12 +84,23 @@ struct pair { B second; }; +template<class Key,class T> +struct flat_map { + using iterator = __gnu_cxx::basic_iterator<std::pair<const Key, T>>; + T& operator[](const Key& key); + iterator begin(); + iterator end(); + iterator find(const Key& key); + iterator erase(iterator); +}; + template<class Key,class T> struct unordered_map { using iterator = __gnu_cxx::basic_iterator<std::pair<const Key, T>>; T& operator[](const Key& key); iterator begin(); iterator end(); + iterator find(const Key& key); iterator erase(iterator); }; diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp index cc48b3d034468..65d676cbe8361 100644 --- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp +++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp @@ -275,9 +275,26 @@ void PointerToVectorElement() { } void SelfInvalidatingMap() { - std::unordered_map<int, int> mp; - mp[1] = 1; - mp[2] = mp[1]; // FIXME: Detect this. We are mising a UseFact for the assignment params. + std::flat_map<int, std::string> mp; + // TODO: We do not have a way to differentiate between pointer stability and iterator stability! + // + // std::unordered_map and other node-based containers provide pointer/reference stability. + // Therefore the following is safe in practice. + // On the other hand, std::flat_map (since C++23) does not provide pointer stability on + // insertion and following is unsafe for this container. + mp[1] = "42"; + mp[2] // expected-note {{invalidated here}} + = + mp[1]; // expected-warning {{object whose reference is captured is later invalidated}} expected-note {{later used here}} +} + +void InvalidateErase() { + std::flat_map<int, std::string> mp; + // None of these containers provide iterator stability. So following is unsafe: + auto it = mp.find(3); // expected-warning {{object whose reference is captured is later invalidated}} + mp.erase(mp.find(4)); // expected-note {{invalidated here}} + if (it != mp.end()) // expected-note {{later used here}} + *it; } } // namespace ElementReferences _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
