https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/180369
>From f7adaa9d5b818db3d036a06188c8d42affb1aa79 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Sat, 4 Jul 2026 16:14:04 +0000 Subject: [PATCH] [LifetimeSafety][NFC] Refactor AccessPath and Loan representations This patch refactors the internal representations of `AccessPath` and `Loan` to support path elements, preparing for field-sensitive and interior-sensitive lifetime tracking. - Introduces `PathElement` representing a field or interior dereference. - Refactors `AccessPath` to contain a base and a list of `PathElement`s. - Updates `Loan` and `LoanManager` to use the new `AccessPath` structure. - Refactors debug dump formatting to output path elements if present. - Updates Checker and FactsGenerator to compile with the new interfaces, keeping logic behaviorally identical (NFC). TAG=agy CONV=2cfd8d00-18d7-4a03-8d78-2aba2f9a8f23 --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 78 +++--- .../Analyses/LifetimeSafety/FactsGenerator.h | 1 + .../Analysis/Analyses/LifetimeSafety/Loans.h | 240 +++++++++++++----- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 28 +- clang/lib/Analysis/LifetimeSafety/Facts.cpp | 60 +++-- .../LifetimeSafety/FactsGenerator.cpp | 58 +++-- .../LifetimeSafety/LifetimeSafety.cpp | 10 +- clang/lib/Analysis/LifetimeSafety/Loans.cpp | 98 +++++-- .../Analysis/LifetimeSafety/MovedLoans.cpp | 2 +- .../Sema/LifetimeSafety/lifetime-facts.cpp | 64 ++--- 10 files changed, 437 insertions(+), 202 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 5c671a93b149c..b4b965c2a8a83 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -20,14 +20,15 @@ #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" -#include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" #include <cstdint> #include <optional> namespace clang::lifetimes::internal { +class LoanPropagationAnalysis; + using FactID = utils::ID<struct FactTag>; /// An abstract base class for a single, atomic lifetime-relevant event. @@ -80,7 +81,8 @@ class Fact { } virtual void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const; + const OriginManager &, + const LoanPropagationAnalysis *LPA = nullptr) const; }; /// A `ProgramPoint` identifies a location in the CFG by pointing to a specific @@ -101,14 +103,17 @@ class IssueFact : public Fact { LoanID getLoanID() const { return LID; } OriginID getOriginID() const { return OID; } void dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &OM) const override; + const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; +/// Represents the expiration of loans at a specific storage location. +/// /// When an AccessPath expires (e.g., a variable goes out of scope), all loans -/// that are associated with this path expire. For example, if `x` expires, then -/// the loan to `x` expires. +/// that are prefixed by this path expire. For example, if `x` expires, then +/// loans to `x`, `x.field`, and `x.field.*` all expire. class ExpireFact : public Fact { - // The access path that expires. + /// The access path that expires (e.g., the variable going out of scope). AccessPath AP; // Expired origin (e.g., its variable goes out of scope). @@ -127,31 +132,41 @@ class ExpireFact : public Fact { SourceLocation getExpiryLoc() const { return ExpiryLoc; } void dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &OM) const override; + const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; class OriginFlowFact : public Fact { OriginID OIDDest; OriginID OIDSrc; - // True if the destination origin should be killed (i.e., its current loans - // cleared) before the source origin's loans are flowed into it. + /// True if the destination origin should be killed (i.e., its current loans + /// cleared) before the source origin's loans are flowed into it. bool KillDest; + /// If set, the source origin's loans are extended by this path element before + /// flowing into the destination. + /// + /// Example: If source has loan to `x` and Element=field, then destination + /// receives loan to `x.field`. This is used for member expressions like + /// `p = obj.field;` where `p` gets a loan to `obj.field`. + std::optional<PathElement> AddToPath; public: static bool classof(const Fact *F) { return F->getKind() == Kind::OriginFlow; } - OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest) + OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest, + std::optional<PathElement> AddToPath = std::nullopt) : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc), - KillDest(KillDest) {} + KillDest(KillDest), AddToPath(AddToPath) {} OriginID getDestOriginID() const { return OIDDest; } OriginID getSrcOriginID() const { return OIDSrc; } bool getKillDest() const { return KillDest; } + std::optional<PathElement> getPathElement() const { return AddToPath; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; /// Represents that an origin escapes the current scope through various means. @@ -191,8 +206,8 @@ class ReturnEscapeFact : public OriginEscapesFact { EscapeKind::Return; } const Expr *getReturnExpr() const { return ReturnExpr; }; - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; /// Represents that an origin escapes via assignment to a field. @@ -211,8 +226,8 @@ class FieldEscapeFact : public OriginEscapesFact { EscapeKind::Field; } const FieldDecl *getFieldDecl() const { return FDecl; }; - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; /// Represents that an origin escapes via assignment to global or static @@ -230,8 +245,8 @@ class GlobalEscapeFact : public OriginEscapesFact { EscapeKind::Global; } const VarDecl *getGlobal() const { return Global; }; - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; class UseFact : public Fact { @@ -253,8 +268,8 @@ class UseFact : public Fact { void markAsWritten() { IsWritten = true; } bool isWritten() const { return IsWritten; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; /// Represents that an origin's storage has been invalidated by a container @@ -276,8 +291,8 @@ class InvalidateOriginFact : public Fact { OriginID getInvalidatedOrigin() const { return OID; } const Expr *getInvalidationExpr() const { return InvalidationExpr; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; /// Top-level origin of the expression which was found to be moved, e.g, when @@ -297,8 +312,8 @@ class MovedOriginFact : public Fact { OriginID getMovedOrigin() const { return MovedOrigin; } const Expr *getMoveExpr() const { return MoveExpr; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; /// A dummy-fact used to mark a specific point in the code for testing. @@ -314,8 +329,8 @@ class TestPointFact : public Fact { StringRef getAnnotation() const { return Annotation; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; /// All loans are cleared from an origin (e.g., assigning a callable without @@ -332,8 +347,8 @@ class KillOriginFact : public Fact { OriginID getKilledOrigin() const { return OID; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM, + const LoanPropagationAnalysis *LPA = nullptr) const override; }; class FactManager { @@ -363,7 +378,8 @@ class FactManager { return Res; } - void dump(const CFG &Cfg, AnalysisDeclContext &AC) const; + void dump(const CFG &Cfg, AnalysisDeclContext &AC, + const LoanPropagationAnalysis *LPA = nullptr) const; /// Retrieves program points that were specially marked in the source code /// for testing. diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 9821078ec1d1e..adb73f563fb17 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -67,6 +67,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { bool hasOrigins(const Expr *E) const; void flow(OriginList *Dst, OriginList *Src, bool Kill, + std::optional<PathElement> AddPath = std::nullopt, const CFGBlock *Block = nullptr); /// Handles assignment for both BinaryOperator and CXXOperatorCallExpr. diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h index cfaae4d574686..372d1456910db 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h @@ -18,6 +18,10 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" #include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Support/raw_ostream.h" namespace clang::lifetimes::internal { @@ -27,126 +31,211 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) { return OS << ID.Value; } -/// Represents the storage location being borrowed, e.g., a specific stack -/// variable or a field within it: var.field.* -/// -/// An AccessPath consists of a root which is one of: -/// - ValueDecl: a local variable or global -/// - MaterializeTemporaryExpr: a temporary object -/// - ParmVarDecl: a function parameter (placeholder) -/// - CXXMethodDecl: the implicit 'this' object (placeholder) -/// - CXXNewExpr: a heap allocation made by `new` -/// -/// Placeholder paths never expire within the function scope, as they represent -/// storage from the caller's scope. +/// Represents one step in an access path: either a field access or an +/// access to an unnamed interior region (denoted by '*'). /// -/// TODO: Model access paths of other types, e.g. field, array subscript, heap -/// allocation not through `new`, and globals. -class AccessPath { +/// Examples: +/// - Field access: `obj.field` has PathElement 'field' +/// - Interior access: `owner.*` has '*' +// - In `std::string s; std::string_view v = s;`, v has loan to s.* +class PathElement { public: - enum class Kind : uint8_t { - ValueDecl, - MaterializeTemporary, - PlaceholderParam, - PlaceholderThis, - NewAllocation, - }; + enum class Kind { Field, Interior }; + + static PathElement getField(const FieldDecl *FD) { + return PathElement(Kind::Field, FD); + } + static PathElement getInterior() { + return PathElement(Kind::Interior, nullptr); + } + + bool isField() const { return K == Kind::Field; } + bool isInterior() const { return K == Kind::Interior; } + const FieldDecl *getFieldDecl() const { return FD; } + + bool operator==(const PathElement &Other) const { + return K == Other.K && FD == Other.FD; + } + bool operator!=(const PathElement &Other) const { return !(*this == Other); } + + void dump(llvm::raw_ostream &OS) const { + if (isField()) + OS << "." << FD->getNameAsString(); + else + OS << ".*"; + } private: + PathElement(Kind K, const FieldDecl *FD) : K(K), FD(FD) {} Kind K; - llvm::PointerUnion<const Expr *, const Decl *> Root; + const FieldDecl *FD; +}; + +/// Represents the base of a placeholder access path, which is either a +/// function parameter or the implicit 'this' object of an instance method. +/// Placeholder paths never expire within the function scope, as they represent +/// storage from the caller's scope. +class PlaceholderBase : public llvm::FoldingSetNode { + llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *> ParamOrMethod; public: - AccessPath(const clang::ValueDecl *D) : K(Kind::ValueDecl), Root(D) {} - AccessPath(const clang::MaterializeTemporaryExpr *MTE) - : K(Kind::MaterializeTemporary), Root(MTE) {} - AccessPath(const CXXNewExpr *New) : K(Kind::NewAllocation), Root(New) {} - static AccessPath Placeholder(const ParmVarDecl *PVD) { - return AccessPath(Kind::PlaceholderParam, PVD); + PlaceholderBase(const ParmVarDecl *PVD) : ParamOrMethod(PVD) {} + PlaceholderBase(const CXXMethodDecl *MD) : ParamOrMethod(MD) {} + + const ParmVarDecl *getParmVarDecl() const { + return ParamOrMethod.dyn_cast<const ParmVarDecl *>(); } - static AccessPath Placeholder(const CXXMethodDecl *MD) { - return AccessPath(Kind::PlaceholderThis, MD); + + const CXXMethodDecl *getMethodDecl() const { + return ParamOrMethod.dyn_cast<const CXXMethodDecl *>(); } - AccessPath(const AccessPath &Other) : K(Other.K), Root(Other.Root) {} - AccessPath &operator=(const AccessPath &) = delete; - Kind getKind() const { return K; } + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddPointer(ParamOrMethod.getOpaqueValue()); + } +}; + +/// Represents the storage location being borrowed, e.g., a specific stack +/// variable or a field within it: var.field.* +/// +/// An AccessPath consists of: +/// - A base: either a ValueDecl, MaterializeTemporaryExpr, or PlaceholderBase +/// - A sequence of PathElements representing field accesses or interior +/// regions +/// +/// Examples: +/// - `x` -> Base=x, Elements=[] +/// - `x.field` -> Base=x, Elements=[.field] +/// - `x.*` (e.g., string_view from string) -> Base=x, Elements=[.*] +/// - `x.field.*` -> Base=x, Elements=[.field, .*] +/// - `$param.field` -> Base=$param, Elements=[.field] +/// +/// TODO: Model access paths of other types, e.g. heap and globals. +class AccessPath { + /// The base of the access path: a variable, temporary, or placeholder. + const llvm::PointerUnion<const clang::ValueDecl *, + const clang::MaterializeTemporaryExpr *, + const PlaceholderBase *, const clang::CXXNewExpr *> + Base; + /// The path elements representing field accesses and access to unnamed + /// interior regions. + llvm::SmallVector<PathElement, 1> Elements; + +public: + AccessPath(const clang::ValueDecl *D) : Base(D) {} + AccessPath(const clang::MaterializeTemporaryExpr *MTE) : Base(MTE) {} + AccessPath(const PlaceholderBase *PB) : Base(PB) {} + AccessPath(const clang::CXXNewExpr *New) : Base(New) {} + + /// Creates an extended access path by appending a path element. + /// Example: AccessPath(x_path, field) creates path to `x.field`. + AccessPath(const AccessPath &Other, PathElement E) + : Base(Other.Base), Elements(Other.Elements) { + Elements.push_back(E); + } const clang::ValueDecl *getAsValueDecl() const { - return K == Kind::ValueDecl - ? cast<const clang::ValueDecl>(cast<const clang::Decl *>(Root)) - : nullptr; + return Base.dyn_cast<const clang::ValueDecl *>(); } + const clang::MaterializeTemporaryExpr *getAsMaterializeTemporaryExpr() const { - return K == Kind::MaterializeTemporary - ? cast<const MaterializeTemporaryExpr>( - cast<const clang::Expr *>(Root)) - : nullptr; - } - const ParmVarDecl *getAsPlaceholderParam() const { - return K == Kind::PlaceholderParam - ? cast<const ParmVarDecl>(cast<const clang::Decl *>(Root)) - : nullptr; + return Base.dyn_cast<const clang::MaterializeTemporaryExpr *>(); } - const CXXMethodDecl *getAsPlaceholderThis() const { - return K == Kind::PlaceholderThis - ? cast<const CXXMethodDecl>(cast<const clang::Decl *>(Root)) - : nullptr; + + const PlaceholderBase *getAsPlaceholderBase() const { + return Base.dyn_cast<const PlaceholderBase *>(); } - const CXXNewExpr *getAsNewAllocation() const { - return K == Kind::NewAllocation - ? cast<const CXXNewExpr>(cast<const clang::Expr *>(Root)) - : nullptr; + + const clang::CXXNewExpr *getAsNewAllocation() const { + return Base.dyn_cast<const clang::CXXNewExpr *>(); } bool operator==(const AccessPath &RHS) const { - return K == RHS.K && Root == RHS.Root; + return Base == RHS.Base && Elements == RHS.Elements; } bool operator!=(const AccessPath &RHS) const { return !(*this == RHS); } - void dump(llvm::raw_ostream &OS) const; -private: - AccessPath(Kind K, const ParmVarDecl *PVD) : K(K), Root(PVD) {} - AccessPath(Kind K, const CXXMethodDecl *MD) : K(K), Root(MD) {} + /// Returns true if this path is a prefix of Other (or same as Other). + /// Examples: + /// - `x` is a prefix of `x`, `x.field`, `x.field.*` + /// - `x.field` is a prefix of `x.field` and `x.field.nested` + /// - `x.field` is NOT a prefix of `x.other_field` + bool isPrefixOf(const AccessPath &Other) const { + if (Base != Other.Base || Elements.size() > Other.Elements.size()) + return false; + for (size_t i = 0; i < Elements.size(); ++i) + if (Elements[i] != Other.Elements[i]) + return false; + return true; + } + + /// Returns true if this path is a strict prefix of Other. + /// Example: + /// - `x` is a strict prefix of `x.field` but NOT of `x` + bool isStrictPrefixOf(const AccessPath &Other) const { + return isPrefixOf(Other) && Elements.size() < Other.Elements.size(); + } + llvm::ArrayRef<PathElement> getElements() const { return Elements; } + + void dump(llvm::raw_ostream &OS) const; }; /// Represents lending a storage location. -/// +// /// A loan tracks the borrowing relationship created by operations like /// taking a pointer/reference (&x), creating a view (std::string_view sv = s), /// or receiving a parameter. /// /// Examples: /// - `int* p = &x;` creates a loan to `x` +/// - `std::string_view v = s;` creates a loan to `s.*` (interior) +/// - `int* p = &obj.field;` creates a loan to `obj.field` /// - Parameter loans have no IssueExpr (created at function entry) class Loan { const LoanID ID; const AccessPath Path; - /// The expression that creates the loan, e.g., &x. Null for placeholder + /// The expression that creates the loan, e.g., &x. Optional for placeholder /// loans. - const Expr *IssuingExpr; + const Expr *IssueExpr; public: - Loan(LoanID ID, AccessPath Path, const Expr *IssuingExpr) - : ID(ID), Path(Path), IssuingExpr(IssuingExpr) {} + Loan(LoanID ID, AccessPath Path, const Expr *IssueExpr = nullptr) + : ID(ID), Path(Path), IssueExpr(IssueExpr) {} + LoanID getID() const { return ID; } const AccessPath &getAccessPath() const { return Path; } - const Expr *getIssuingExpr() const { return IssuingExpr; } + const Expr *getIssueExpr() const { return IssueExpr; } + void dump(llvm::raw_ostream &OS) const; }; /// Manages the creation, storage and retrieval of loans. class LoanManager { + using ExtensionCacheKey = std::pair<LoanID, PathElement>; + public: LoanManager() = default; - Loan *createLoan(AccessPath Path, const Expr *IssueExpr) { + Loan *createLoan(AccessPath Path, const Expr *IssueExpr = nullptr) { void *Mem = LoanAllocator.Allocate<Loan>(); auto *NewLoan = new (Mem) Loan(getNextLoanID(), Path, IssueExpr); AllLoans.push_back(NewLoan); return NewLoan; } + /// Gets or creates a placeholder base for a given parameter or method. + const PlaceholderBase *getOrCreatePlaceholderBase(const ParmVarDecl *PVD); + const PlaceholderBase *getOrCreatePlaceholderBase(const CXXMethodDecl *MD); + + /// Gets or creates a loan by extending BaseLoanID with Element. + /// Caches the result to ensure convergence in LoanPropagation. + Loan *getOrCreateExtendedLoan(LoanID BaseLoanID, PathElement Element); + + /// Finds the base loan IDs that could have been extended to produce + /// ExtendedLoanID. + llvm::SmallVector<LoanID, 2> getBaseLoans(LoanID ExtendedLoanID, + PathElement Element) const; + const Loan *getLoan(LoanID ID) const { assert(ID.Value < AllLoans.size()); return AllLoans[ID.Value]; @@ -158,6 +247,14 @@ class LoanManager { LoanID getNextLoanID() { return NextLoanID++; } LoanID NextLoanID{0}; + + llvm::FoldingSet<PlaceholderBase> PlaceholderBases; + /// Cache for extended loans. Maps (BaseLoanID, PathElement) to the extended + /// loan. Ensures that extending the same loan with the same path element + /// always returns the same loan object, which is necessary for dataflow + /// analysis convergence. + llvm::DenseMap<ExtensionCacheKey, Loan *> ExtensionCache; + /// TODO(opt): Profile and evaluate the usefullness of small buffer /// optimisation. llvm::SmallVector<const Loan *> AllLoans; @@ -165,4 +262,15 @@ class LoanManager { }; } // namespace clang::lifetimes::internal +namespace llvm { +template <> struct DenseMapInfo<clang::lifetimes::internal::PathElement> { + using PathElement = clang::lifetimes::internal::PathElement; + static unsigned getHashValue(const PathElement &Val) { + return llvm::hash_combine(Val.isInterior(), Val.getFieldDecl()); + } + static bool isEqual(const PathElement &LHS, const PathElement &RHS) { + return LHS == RHS; + } +}; +} // namespace llvm #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 746ebbfb15c39..074c34b1a9aaf 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -164,10 +164,12 @@ class LifetimeChecker { auto MovedAtEscape = MovedLoans.getMovedLoans(OEF); for (LoanID LID : EscapedLoans) { const Loan *L = FactMgr.getLoanMgr().getLoan(LID); - const AccessPath &AP = L->getAccessPath(); - if (const auto *PVD = AP.getAsPlaceholderParam()) + const PlaceholderBase *PB = L->getAccessPath().getAsPlaceholderBase(); + if (!PB) + continue; + if (const auto *PVD = PB->getParmVarDecl()) CheckParam(PVD, /*IsMoved=*/MovedAtEscape.lookup(LID)); - else if (const auto *MD = AP.getAsPlaceholderThis()) + else if (const auto *MD = PB->getMethodDecl()) CheckImplicitThis(MD); } } @@ -175,7 +177,8 @@ class LifetimeChecker { /// Checks for use-after-free & use-after-return errors when an access path /// expires (e.g., a variable goes out of scope). /// - /// When a path expires, all loans having this path expires. + /// When a path expires, all loans prefixed by that path expire. For example, + /// if `x` expires, loans to `x`, `x.field`, and `x.field.*` all expire. /// This method examines all live origins and reports warnings for loans they /// hold that are prefixed by the expired path. void checkExpiry(const ExpireFact *EF) { @@ -207,9 +210,11 @@ class LifetimeChecker { /// Checks for use-after-invalidation errors when a container is modified. /// - /// This method identifies origins that are live at the point of invalidation - /// and checks if they hold loans that are invalidated by the operation - /// (e.g., iterators into a vector that is being pushed to). + /// When a container is invalidated, loans pointing into its interior are + /// invalidated. For example, if container `v` is invalidated, iterators with + /// loans to `v.*` are invalidated. This method finds live origins holding + /// such loans and reports warnings. A loan is invalidated if its path extends + /// an invalidated container's path (e.g., `v.*` extends `v`). void checkInvalidation(const InvalidateOriginFact *IOF) { OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin(); /// Get loans directly pointing to the invalidated container @@ -249,16 +254,19 @@ class LifetimeChecker { return; for (const auto &[LID, Warning] : FinalWarningsMap) { const Loan *L = FactMgr.getLoanMgr().getLoan(LID); - const Expr *IssueExpr = L->getIssuingExpr(); + const Expr *IssueExpr = L->getIssueExpr(); + const ParmVarDecl *InvalidatedPVD = nullptr; + if (const PlaceholderBase *PB = L->getAccessPath().getAsPlaceholderBase()) + InvalidatedPVD = PB->getParmVarDecl(); + llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact = Warning.CausingFact; - const ParmVarDecl *InvalidatedPVD = - L->getAccessPath().getAsPlaceholderParam(); const Expr *MovedExpr = Warning.MovedExpr; SourceLocation ExpiryLoc = Warning.ExpiryLoc; if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) { if (Warning.InvalidatedByExpr) { + // Use-after-invalidation of an object on stack. if (IssueExpr) // Use-after-invalidation of an object on stack. SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(), diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 3d7fbcdacc830..5dfc2c37f8852 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -8,17 +8,19 @@ #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "clang/AST/Decl.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" #include "clang/Analysis/Analyses/PostOrderCFGView.h" namespace clang::lifetimes::internal { void Fact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const { + const OriginManager &, const LoanPropagationAnalysis *) const { OS << "Fact (Kind: " << static_cast<int>(K) << ")\n"; } void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "Issue ("; LM.getLoan(getLoanID())->dump(OS); OS << ", ToOrigin: "; @@ -27,7 +29,8 @@ void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM, } void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *LPA) const { OS << "Expire ("; getAccessPath().dump(OS); if (auto OID = getOriginID()) { @@ -37,48 +40,71 @@ void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM, OS << ")\n"; } -void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { +void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &LM, + const OriginManager &OM, + const LoanPropagationAnalysis *LPA) const { OS << "OriginFlow: \n"; OS << "\tDest: "; OM.dump(getDestOriginID(), OS); + if (LPA) { + LoanSet DestinationLoans = LPA->getLoans(getDestOriginID(), this); + if (DestinationLoans.isEmpty()) + OS << " has no loans"; + else { + OS << " has loans to { "; + for (LoanID LID : DestinationLoans) { + LM.getLoan(LID)->getAccessPath().dump(OS); + OS << " "; + } + OS << "}"; + } + } OS << "\n"; OS << "\tSrc: "; OM.dump(getSrcOriginID(), OS); OS << (getKillDest() ? "" : ", Merge"); + if (auto Path = getPathElement()) { + OS << "\n\tAdd path: "; + Path->dump(OS); + } OS << "\n"; } void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "MovedOrigins ("; OM.dump(getMovedOrigin(), OS); OS << ")\n"; } void ReturnEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "OriginEscapes ("; OM.dump(getEscapedOriginID(), OS); OS << ", via Return)\n"; } void FieldEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "OriginEscapes ("; OM.dump(getEscapedOriginID(), OS); OS << ", via Field)\n"; } void GlobalEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "OriginEscapes ("; OM.dump(getEscapedOriginID(), OS); OS << ", via Global)\n"; } void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "Use ("; size_t NumUsedOrigins = getUsedOrigins()->getLength(); size_t I = 0; @@ -92,19 +118,22 @@ void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &, } void InvalidateOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "InvalidateOrigin ("; OM.dump(getInvalidatedOrigin(), OS); OS << ")\n"; } void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const { + const OriginManager &, + const LoanPropagationAnalysis *) const { OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n"; } void KillOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { + const OriginManager &OM, + const LoanPropagationAnalysis *) const { OS << "KillOrigin ("; OM.dump(getKilledOrigin(), OS); OS << ")\n"; @@ -125,7 +154,8 @@ llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const { return AnnotationToPointMap; } -void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const { +void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC, + const LoanPropagationAnalysis *LPA) const { llvm::dbgs() << "==========================================\n"; llvm::dbgs() << " Lifetime Analysis Facts:\n"; llvm::dbgs() << "==========================================\n"; @@ -137,7 +167,7 @@ void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const { llvm::dbgs() << " Block B" << B->getBlockID() << ":\n"; for (const Fact *F : getFacts(B)) { llvm::dbgs() << " "; - F->dump(llvm::dbgs(), LoanMgr, OriginMgr); + F->dump(llvm::dbgs(), LoanMgr, OriginMgr, LPA); } llvm::dbgs() << " End of Block\n"; } diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 095bee8135c20..8de6aec04a871 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -52,6 +52,10 @@ bool FactsGenerator::hasOrigins(const Expr *E) const { /// have the same shape (same depth/structure). This invariant ensures that /// origins flow only between compatible types during expression evaluation. /// +/// If AddPath is specified, loans from the source are extended by the given +/// path element before flowing to the destination. This is used for field +/// accesses and interior borrows. +/// /// Examples: /// - `int* p = &x;` flows origins from `&x` (depth 1) to `p` (depth 1) /// - `int** pp = &p;` flows origins from `&p` (depth 2) to `pp` (depth 2) @@ -63,10 +67,13 @@ bool FactsGenerator::hasOrigins(const Expr *E) const { /// \param Src The source origin list. /// \param Kill If true, the destination's existing loans are killed before /// flowing. +/// \param AddPath Optional. If provided, loans are extended with this path +/// element. /// \param Block Optional. If provided, the generated flow facts are appended to /// this specific CFG block. Otherwise, they are appended to the /// current block being visited. void FactsGenerator::flow(OriginList *Dst, OriginList *Src, bool Kill, + std::optional<PathElement> AddPath, const CFGBlock *Block) { if (!Dst) return; @@ -76,8 +83,8 @@ void FactsGenerator::flow(OriginList *Dst, OriginList *Src, bool Kill, "Lists must have the same length"); while (Dst && Src) { - Fact *F = FactMgr.createFact<OriginFlowFact>(Dst->getOuterOriginID(), - Src->getOuterOriginID(), Kill); + Fact *F = FactMgr.createFact<OriginFlowFact>( + Dst->getOuterOriginID(), Src->getOuterOriginID(), Kill, AddPath); if (Block) FactMgr.appendBlockFact(Block, F); else @@ -274,17 +281,16 @@ void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) { auto *MD = ME->getMemberDecl(); - if (isa<FieldDecl>(MD) && doesDeclHaveStorage(MD)) { + if (auto *FD = dyn_cast<FieldDecl>(MD); FD && doesDeclHaveStorage(FD)) { assert(ME->isGLValue() && "Field member should be GL value"); OriginList *Dst = getOriginsList(*ME); assert(Dst && "Field member should have an origin list as it is GL value"); OriginList *Src = getOriginsList(*ME->getBase()); assert(Src && "Base expression should be a pointer/reference type"); - // The field's glvalue (outermost origin) holds the same loans as the base - // expression. + // Flow loans from base to field, but do not append field path element yet (NFC). CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( Dst->getOuterOriginID(), Src->getOuterOriginID(), - /*Kill=*/true)); + /*Kill=*/true, std::nullopt)); } } @@ -602,10 +608,11 @@ void FactsGenerator::VisitAbstractConditionalOperator( const Expr *FalseExpr = CO->getFalseExpr(); if (const CFGBlock *TBPred = findPredBlockForExpr(CurrentBlock, TrueExpr)) - flow(getOriginsList(*CO), getOriginsList(*TrueExpr), /*Kill=*/true, TBPred); + flow(getOriginsList(*CO), getOriginsList(*TrueExpr), /*Kill=*/true, + std::nullopt, TBPred); if (const CFGBlock *FBPred = findPredBlockForExpr(CurrentBlock, FalseExpr)) flow(getOriginsList(*CO), getOriginsList(*FalseExpr), /*Kill=*/true, - FBPred); + std::nullopt, FBPred); } void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { @@ -955,8 +962,7 @@ void FactsGenerator::handleInvalidatingCall(const Expr *Call, return; // Heuristics to turn-down false positives. Skip member field expressions for - // now. This is not a perfect filter and will still surface some false - // positives (e.g. `auto& r = s.v`). + // now (NFC). if (!isa<DeclRefExpr>(Args[0]->IgnoreImpCasts())) return; @@ -1069,6 +1075,12 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, } } +static std::optional<PathElement> +getPathElementForLifetimeBoundArg(const FunctionDecl *FD, unsigned ArgIndex, + const Expr *ArgExpr) { + return std::nullopt; +} + void FactsGenerator::handleFunctionCall(const Expr *Call, const FunctionDecl *FD, ArrayRef<const Expr *> Args, @@ -1142,13 +1154,13 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, assert(!Args[I]->isGLValue() || ArgList->getLength() >= 2); ArgList = getRValueOrigins(Args[I], ArgList); } + // std::string_view(const std::string& from) if (isGslOwnerType(Args[I]->getType())) { - // The constructed gsl::Pointer borrows from the Owner's storage, not - // from what the Owner itself borrows, so only the outermost origin is - // needed. + // GSL construction creates a view that borrows from arguments. + // Only flow the outer origin because inner lengths may mismatch. CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( - CallList->getOuterOriginID(), ArgList->getOuterOriginID(), - KillSrc)); + CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc, + std::nullopt)); KillSrc = false; } else if (IsArgLifetimeBound(I)) { // Only flow the outer origin here. For lifetimebound args in @@ -1162,7 +1174,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, KillSrc)); KillSrc = false; } - } else if (shouldTrackPointerImplicitObjectArg(I)) { + } else if (I == 0 && shouldTrackPointerImplicitObjectArg(I)) { assert(ArgList->getLength() >= 2 && "Object arg of pointer type should have at least two origins"); // See through the GSLPointer reference to see the pointer's value. @@ -1175,7 +1187,8 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, // pointer/reference itself must not outlive the arguments. This // only constrains the top-level origin. CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( - CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); + CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc, + getPathElementForLifetimeBoundArg(FD, I, Args[I]))); KillSrc = false; } } @@ -1237,9 +1250,9 @@ llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() { llvm::SmallVector<Fact *> PlaceholderLoanFacts; if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins()) { OriginList *List = *ThisOrigins; - const Loan *L = FactMgr.getLoanMgr().createLoan( - AccessPath::Placeholder(cast<CXXMethodDecl>(FD)), - /*IssuingExpr=*/nullptr); + const PlaceholderBase *PB = FactMgr.getLoanMgr().getOrCreatePlaceholderBase( + cast<CXXMethodDecl>(FD)); + const Loan *L = FactMgr.getLoanMgr().createLoan(AccessPath(PB)); PlaceholderLoanFacts.push_back( FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID())); } @@ -1247,8 +1260,9 @@ llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() { OriginList *List = getOriginsList(*PVD); if (!List) continue; - const Loan *L = FactMgr.getLoanMgr().createLoan( - AccessPath::Placeholder(PVD), /*IssuingExpr=*/nullptr); + const PlaceholderBase *PB = + FactMgr.getLoanMgr().getOrCreatePlaceholderBase(PVD); + const Loan *L = FactMgr.getLoanMgr().createLoan(AccessPath(PB)); PlaceholderLoanFacts.push_back( FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID())); } diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp index 680095a54177c..798812327d3ab 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp @@ -35,7 +35,8 @@ namespace internal { #ifndef NDEBUG static void DebugOnlyFunction(AnalysisDeclContext &AC, const CFG &Cfg, - FactManager &FactMgr) { + FactManager &FactMgr, + const LoanPropagationAnalysis *LPA) { std::string Name; if (const Decl *D = AC.getDecl()) { if (const auto *ND = dyn_cast<NamedDecl>(D)) @@ -44,7 +45,7 @@ static void DebugOnlyFunction(AnalysisDeclContext &AC, const CFG &Cfg, DEBUG_WITH_TYPE(Name.c_str(), AC.getDecl()->dumpColor()); DEBUG_WITH_TYPE(Name.c_str(), Cfg.dump(AC.getASTContext().getLangOpts(), /*ShowColors=*/true)); - DEBUG_WITH_TYPE(Name.c_str(), FactMgr.dump(Cfg, AC)); + DEBUG_WITH_TYPE(Name.c_str(), FactMgr.dump(Cfg, AC, LPA)); } #endif @@ -101,12 +102,13 @@ void LifetimeSafetyAnalysis::run() { DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(), /*ShowColors=*/true)); - DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC)); + DEBUG_WITH_TYPE("LifetimeFacts", + FactMgr->dump(Cfg, AC, LoanPropagation.get())); // Debug print facts for a specific function using // -debug-only=EnableFilterByFunctionName,YourFunctionNameFoo DEBUG_WITH_TYPE("EnableFilterByFunctionName", - DebugOnlyFunction(AC, Cfg, *FactMgr)); + DebugOnlyFunction(AC, Cfg, *FactMgr, LoanPropagation.get())); DEBUG_WITH_TYPE("LiveOrigins", LiveOrigins->dump(llvm::dbgs(), FactMgr->getTestPoints())); } diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp index c0c8d0b283f8b..9692b5ff33f48 100644 --- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp @@ -11,28 +11,22 @@ namespace clang::lifetimes::internal { void AccessPath::dump(llvm::raw_ostream &OS) const { - switch (K) { - case Kind::ValueDecl: - if (const clang::ValueDecl *VD = getAsValueDecl()) - OS << VD->getNameAsString(); - break; - case Kind::MaterializeTemporary: - if (const clang::MaterializeTemporaryExpr *MTE = - getAsMaterializeTemporaryExpr()) - OS << "MaterializeTemporaryExpr at " << MTE; - break; - case Kind::PlaceholderParam: - if (const auto *PVD = getAsPlaceholderParam()) + if (const clang::ValueDecl *VD = getAsValueDecl()) + OS << VD->getNameAsString(); + else if (const clang::MaterializeTemporaryExpr *MTE = + getAsMaterializeTemporaryExpr()) + OS << "MaterializeTemporaryExpr at " << MTE; + else if (const PlaceholderBase *PB = getAsPlaceholderBase()) { + if (const auto *PVD = PB->getParmVarDecl()) OS << "$" << PVD->getNameAsString(); - break; - case Kind::PlaceholderThis: - OS << "$this"; - break; - case Kind::NewAllocation: - if (const auto *E = getAsNewAllocation()) - OS << "NewAllocation at " << E; - break; - } + else if (PB->getMethodDecl()) + OS << "$this"; + } else if (const auto *E = getAsNewAllocation()) + OS << "NewAllocation at " << E; + else + llvm_unreachable("access path base invalid"); + for (const auto &E : Elements) + E.dump(OS); } void Loan::dump(llvm::raw_ostream &OS) const { @@ -40,4 +34,66 @@ void Loan::dump(llvm::raw_ostream &OS) const { Path.dump(OS); OS << ")"; } + +const PlaceholderBase * +LoanManager::getOrCreatePlaceholderBase(const ParmVarDecl *PVD) { + llvm::FoldingSetNodeID ID; + ID.AddPointer(PVD); + void *InsertPos = nullptr; + if (PlaceholderBase *Existing = + PlaceholderBases.FindNodeOrInsertPos(ID, InsertPos)) + return Existing; + + void *Mem = LoanAllocator.Allocate<PlaceholderBase>(); + PlaceholderBase *NewPB = new (Mem) PlaceholderBase(PVD); + PlaceholderBases.InsertNode(NewPB, InsertPos); + return NewPB; +} + +const PlaceholderBase * +LoanManager::getOrCreatePlaceholderBase(const CXXMethodDecl *MD) { + llvm::FoldingSetNodeID ID; + ID.AddPointer(MD); + void *InsertPos = nullptr; + if (PlaceholderBase *Existing = + PlaceholderBases.FindNodeOrInsertPos(ID, InsertPos)) + return Existing; + + void *Mem = LoanAllocator.Allocate<PlaceholderBase>(); + PlaceholderBase *NewPB = new (Mem) PlaceholderBase(MD); + PlaceholderBases.InsertNode(NewPB, InsertPos); + return NewPB; +} + +Loan *LoanManager::getOrCreateExtendedLoan(LoanID BaseLoanID, + PathElement Element) { + + ExtensionCacheKey Key = {BaseLoanID, Element}; + auto It = ExtensionCache.find(Key); + if (It != ExtensionCache.end()) + return It->second; + const auto *BaseLoan = getLoan(BaseLoanID); + // TODO: Polish comment: Do not add interior access if the base loan path + // already contains that at the end. + if (Element.isInterior() && + !BaseLoan->getAccessPath().getElements().empty() && + BaseLoan->getAccessPath().getElements().back().isInterior()) + return ExtensionCache[Key] = const_cast<Loan *>(BaseLoan); + + AccessPath ExtendedPath(BaseLoan->getAccessPath(), Element); + return ExtensionCache[Key] = + createLoan(ExtendedPath, BaseLoan->getIssueExpr()); +} + +llvm::SmallVector<LoanID, 2> +LoanManager::getBaseLoans(LoanID ExtendedLoanID, PathElement Element) const { + llvm::SmallVector<LoanID, 2> Result; + for (const auto &Entry : ExtensionCache) { + if (Entry.second->getID() == ExtendedLoanID && + Entry.first.second == Element) { + Result.push_back(Entry.first.first); + } + } + return Result; +} } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp b/clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp index 138704821024a..1a9ce3e576733 100644 --- a/clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp +++ b/clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp @@ -80,7 +80,7 @@ class AnalysisImpl auto IsInvalidated = [&](const AccessPath &Path) { for (LoanID LID : ImmediatelyMovedLoans) { const Loan *MovedLoan = LoanMgr.getLoan(LID); - if (MovedLoan->getAccessPath() == Path) + if (MovedLoan->getAccessPath().isPrefixOf(Path)) return true; } return false; diff --git a/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp b/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp index d655bf7391203..028833902aa94 100644 --- a/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp +++ b/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp @@ -23,7 +23,7 @@ MyObj* return_local_addr() { return p; // CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) has loans to { x } // CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : MyObj *) // CHECK: Expire (x) // CHECK: Expire (p, Origin: [[O_P]] (Decl: p, Type : MyObj *)) @@ -37,11 +37,11 @@ void loan_expires_cpp() { // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_OBJ:[0-9]+]] (Path: obj), ToOrigin: [[O_DRE_OBJ:[0-9]+]] (Expr: DeclRefExpr, Decl: obj)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_ADDR_OBJ:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *) has loans to { obj } // CHECK-NEXT: Src: [[O_DRE_OBJ]] (Expr: DeclRefExpr, Decl: obj) MyObj* pObj = &obj; // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pObj, Type : MyObj *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pObj, Type : MyObj *) has loans to { obj } // CHECK-NEXT: Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator, Type : MyObj *) // CHECK: Expire (obj) } @@ -53,11 +53,11 @@ void loan_expires_trivial() { // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj), ToOrigin: [[O_DRE_TRIVIAL:[0-9]+]] (Expr: DeclRefExpr, Decl: trivial_obj)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator, Type : int *) +// CHECK-NEXT: Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator, Type : int *) has loans to { trivial_obj } // CHECK-NEXT: Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr, Decl: trivial_obj) int* pTrivialObj = &trivial_obj; // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pTrivialObj, Type : int *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pTrivialObj, Type : int *) has loans to { trivial_obj } // CHECK-NEXT: Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator, Type : int *) // CHECK: Expire (trivial_obj) // CHECK-NEXT: End of Block @@ -79,12 +79,12 @@ void overwrite_origin() { p = &s2; // CHECK: Issue ([[L_S2:[0-9]+]] (Path: s2), ToOrigin: [[O_DRE_S2:[0-9]+]] (Expr: DeclRefExpr, Decl: s2)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *) has loans to { s2 } // CHECK-NEXT: Src: [[O_DRE_S2]] (Expr: DeclRefExpr, Decl: s2) // CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Write) // CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *) has loans to { s2 } // CHECK-NEXT: Src: [[O_ADDR_S2]] (Expr: UnaryOperator, Type : MyObj *) // CHECK: Expire (s2) // CHECK: Expire (s1) @@ -106,7 +106,7 @@ void reassign_to_null() { // CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Write) // CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *) has no loans // CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : MyObj *) // CHECK: Expire (s1) } @@ -120,25 +120,25 @@ void pointer_indirection() { // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_A:[0-9]+]] (Path: a), ToOrigin: [[O_DRE_A:[0-9]+]] (Expr: DeclRefExpr, Decl: a)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator, Type : int *) +// CHECK-NEXT: Dest: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator, Type : int *) has loans to { a } // CHECK-NEXT: Src: [[O_DRE_A]] (Expr: DeclRefExpr, Decl: a) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : int *) +// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : int *) has loans to { a } // CHECK-NEXT: Src: [[O_ADDR_A]] (Expr: UnaryOperator, Type : int *) int **pp = &p; // CHECK: Use ([[O_P]] (Decl: p, Type : int *), Read) // CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int **) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int **) has loans to { p } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *) has loans to { a } // CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : int *) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_PP_OUTER:[0-9]+]] (Decl: pp, Type : int **) +// CHECK-NEXT: Dest: [[O_PP_OUTER:[0-9]+]] (Decl: pp, Type : int **) has loans to { p } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : int **) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_PP_INNER:[0-9]+]] (Decl: pp, Type : int *) +// CHECK-NEXT: Dest: [[O_PP_INNER:[0-9]+]] (Decl: pp, Type : int *) has loans to { a } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : int *) // FIXME: Propagate origins across dereference unary operator* @@ -146,22 +146,22 @@ void pointer_indirection() { // CHECK: Use ([[O_PP_OUTER]] (Decl: pp, Type : int **), [[O_PP_INNER]] (Decl: pp, Type : int *), Read) // CHECK: Issue ({{[0-9]+}} (Path: pp), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: pp)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int **) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int **) has loans to { p } // CHECK-NEXT: Src: [[O_PP_OUTER]] (Decl: pp, Type : int **) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) has loans to { a } // CHECK-NEXT: Src: [[O_PP_INNER]] (Decl: pp, Type : int *) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *&) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *&) has loans to { p } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int **) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *) has loans to { a } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) has loans to { a } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : int *) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: q, Type : int *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: q, Type : int *) has loans to { a } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) } @@ -180,30 +180,30 @@ void test_use_lifetimebound_call() { MyObj *q = &y; // CHECK: Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] (Expr: DeclRefExpr, Decl: y)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *) has loans to { y } // CHECK-NEXT: Src: [[O_DRE_Y]] (Expr: DeclRefExpr, Decl: y) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_Q:[0-9]+]] (Decl: q, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_Q:[0-9]+]] (Decl: q, Type : MyObj *) has loans to { y } // CHECK-NEXT: Src: [[O_ADDR_Y]] (Expr: UnaryOperator, Type : MyObj *) MyObj* r = LifetimeBoundCall(p, q); // CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Read) // CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) has loans to { x } // CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : MyObj *) // CHECK: Use ([[O_Q]] (Decl: q, Type : MyObj *), Read) // CHECK: Issue ({{[0-9]+}} (Path: q), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: q)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_Q_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_Q_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) has loans to { y } // CHECK-NEXT: Src: [[O_Q]] (Decl: q, Type : MyObj *) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_CALL_EXPR:[0-9]+]] (Expr: CallExpr, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_CALL_EXPR:[0-9]+]] (Expr: CallExpr, Type : MyObj *) has loans to { x } // CHECK-NEXT: Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr, Type : MyObj *) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_CALL_EXPR]] (Expr: CallExpr, Type : MyObj *) +// CHECK-NEXT: Dest: [[O_CALL_EXPR]] (Expr: CallExpr, Type : MyObj *) has loans to { x y } // CHECK-NEXT: Src: [[O_Q_RVAL]] (Expr: ImplicitCastExpr, Type : MyObj *), Merge // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: r, Type : MyObj *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: r, Type : MyObj *) has loans to { x y } // CHECK-NEXT: Src: [[O_CALL_EXPR]] (Expr: CallExpr, Type : MyObj *) // CHECK: Expire (y) // CHECK: Expire (x) @@ -217,23 +217,23 @@ void test_reference_variable() { // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr, Decl: x)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_CAST_Y:[0-9]+]] (Expr: ImplicitCastExpr, Type : const MyObj &) +// CHECK-NEXT: Dest: [[O_CAST_Y:[0-9]+]] (Expr: ImplicitCastExpr, Type : const MyObj &) has loans to { x } // CHECK-NEXT: Src: [[O_DRE_X]] (Expr: DeclRefExpr, Decl: x) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_Y:[0-9]+]] (Decl: y, Type : const MyObj &) +// CHECK-NEXT: Dest: [[O_Y:[0-9]+]] (Decl: y, Type : const MyObj &) has loans to { x } // CHECK-NEXT: Src: [[O_CAST_Y]] (Expr: ImplicitCastExpr, Type : const MyObj &) const MyObj& z = y; // CHECK: OriginFlow: -// CHECK-NEXT: Dest: [[O_Z:[0-9]+]] (Decl: z, Type : const MyObj &) +// CHECK-NEXT: Dest: [[O_Z:[0-9]+]] (Decl: z, Type : const MyObj &) has loans to { x } // CHECK-NEXT: Src: [[O_Y]] (Decl: y, Type : const MyObj &) p = &z; // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *) has loans to { x } // CHECK-NEXT: Src: [[O_Z]] (Decl: z, Type : const MyObj &) // CHECK: Use ({{[0-9]+}} (Decl: p, Type : const MyObj *), Write) // CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p)) // CHECK: OriginFlow: -// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: p, Type : const MyObj *) +// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: p, Type : const MyObj *) has loans to { x } // CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *) // CHECK: Expire (x) } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
