diff options
Diffstat (limited to 'clang')
71 files changed, 2469 insertions, 2348 deletions
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 99aa545..65b086c 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -180,6 +180,9 @@ C Language Changes C2y Feature Support ^^^^^^^^^^^^^^^^^^^ +- No longer triggering ``-Wstatic-in-inline`` in C2y mode; use of a static + function or variable within an extern inline function is no longer a + constraint per `WG14 N3622 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3622.txt>`_. - Clang now supports `N3355 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm>`_ Named Loops. C23 Feature Support diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index e1a4005..573cc72 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -7160,6 +7160,18 @@ public: /// Return original type of the base expression for array section. static QualType getBaseOriginalType(const Expr *Base); + /// Return the effective 'element' type of this array section. As the array + /// section itself returns a collection of elements (closer to its `getBase` + /// type), this is only useful for figuring out the effective type of this if + /// it were a normal Array subscript expr. + QualType getElementType() const; + + /// Returns the effective 'type' of the base of this array section. This + /// should be the array/pointer type that this operates on. Just + /// getBase->getType isn't sufficient, since it doesn't look through existing + /// Array sections to figure out the actual 'base' of this. + QualType getBaseType() const; + static bool classof(const Stmt *T) { return T->getStmtClass() == ArraySectionExprClass; } diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeAnnotations.h index f02969e..229d16c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeAnnotations.h @@ -12,7 +12,8 @@ #include "clang/AST/DeclCXX.h" -namespace clang ::lifetimes { +namespace clang { +namespace lifetimes { /// Returns the most recent declaration of the method to ensure all /// lifetime-bound attributes from redeclarations are considered. @@ -37,7 +38,7 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD); /// lifetimebound, either due to an explicit lifetimebound attribute on the /// method or because it's a normal assignment operator. bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD); - -} // namespace clang::lifetimes +} // namespace lifetimes +} // namespace clang #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h new file mode 100644 index 0000000..e54fc26 --- /dev/null +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h @@ -0,0 +1,183 @@ +//===- LifetimeSafety.h - C++ Lifetime Safety Analysis -*----------- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the entry point for a dataflow-based static analysis +// that checks for C++ lifetime violations. +// +// The analysis is based on the concepts of "origins" and "loans" to track +// pointer lifetimes and detect issues like use-after-free and dangling +// pointers. See the RFC for more details: +// https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291 +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H +#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/ImmutableSet.h" +#include "llvm/ADT/StringMap.h" +#include <memory> + +namespace clang::lifetimes { + +/// Enum to track the confidence level of a potential error. +enum class Confidence : uint8_t { + None, + Maybe, // Reported as a potential error (-Wlifetime-safety-strict) + Definite // Reported as a definite error (-Wlifetime-safety-permissive) +}; + +enum class LivenessKind : uint8_t { + Dead, // Not alive + Maybe, // Live on some path but not all paths (may-be-live) + Must // Live on all paths (must-be-live) +}; + +class LifetimeSafetyReporter { +public: + LifetimeSafetyReporter() = default; + virtual ~LifetimeSafetyReporter() = default; + + virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, + SourceLocation FreeLoc, + Confidence Confidence) {} +}; + +/// The main entry point for the analysis. +void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter); + +namespace internal { +// Forward declarations of internal types. +class Fact; +class FactManager; +class LoanPropagationAnalysis; +class ExpiredLoansAnalysis; +class LiveOriginAnalysis; +struct LifetimeFactory; + +/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type. +/// Used for giving ID to loans and origins. +template <typename Tag> struct ID { + uint32_t Value = 0; + + bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; } + bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); } + bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; } + ID<Tag> operator++(int) { + ID<Tag> Tmp = *this; + ++Value; + return Tmp; + } + void Profile(llvm::FoldingSetNodeID &IDBuilder) const { + IDBuilder.AddInteger(Value); + } +}; + +using LoanID = ID<struct LoanTag>; +using OriginID = ID<struct OriginTag>; +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) { + return OS << ID.Value; +} +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) { + return OS << ID.Value; +} + +// Using LLVM's immutable collections is efficient for dataflow analysis +// as it avoids deep copies during state transitions. +// TODO(opt): Consider using a bitset to represent the set of loans. +using LoanSet = llvm::ImmutableSet<LoanID>; +using OriginSet = llvm::ImmutableSet<OriginID>; +using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>; + +/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific +/// `Fact`. identified by a lifetime-related event (`Fact`). +/// +/// A `ProgramPoint` has "after" semantics: it represents the location +/// immediately after its corresponding `Fact`. +using ProgramPoint = const Fact *; + +/// Running the lifetime safety analysis and querying its results. It +/// encapsulates the various dataflow analyses. +class LifetimeSafetyAnalysis { +public: + LifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter); + ~LifetimeSafetyAnalysis(); + + void run(); + + /// Returns the set of loans an origin holds at a specific program point. + LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const; + + /// Returns the set of origins that are live at a specific program point, + /// along with the confidence level of their liveness. + /// + /// An origin is considered live if there are potential future uses of that + /// origin after the given program point. The confidence level indicates + /// whether the origin is definitely live (Definite) due to being domintated + /// by a set of uses or only possibly live (Maybe) only on some but not all + /// control flow paths. + std::vector<std::pair<OriginID, LivenessKind>> + getLiveOriginsAtPoint(ProgramPoint PP) const; + + /// Finds the OriginID for a given declaration. + /// Returns a null optional if not found. + std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const; + + /// Finds the LoanID's for the loan created with the specific variable as + /// their Path. + std::vector<LoanID> getLoanIDForVar(const VarDecl *VD) const; + + /// Retrieves program points that were specially marked in the source code + /// for testing. + /// + /// The analysis recognizes special function calls of the form + /// `void("__lifetime_test_point_<name>")` as test points. This method returns + /// a map from the annotation string (<name>) to the corresponding + /// `ProgramPoint`. This allows test harnesses to query the analysis state at + /// user-defined locations in the code. + /// \note This is intended for testing only. + llvm::StringMap<ProgramPoint> getTestPoints() const; + +private: + AnalysisDeclContext &AC; + LifetimeSafetyReporter *Reporter; + std::unique_ptr<LifetimeFactory> Factory; + std::unique_ptr<FactManager> FactMgr; + std::unique_ptr<LoanPropagationAnalysis> LoanPropagation; + std::unique_ptr<LiveOriginAnalysis> LiveOrigins; +}; +} // namespace internal +} // namespace clang::lifetimes + +namespace llvm { +template <typename Tag> +struct DenseMapInfo<clang::lifetimes::internal::ID<Tag>> { + using ID = clang::lifetimes::internal::ID<Tag>; + + static inline ID getEmptyKey() { + return {DenseMapInfo<uint32_t>::getEmptyKey()}; + } + + static inline ID getTombstoneKey() { + return {DenseMapInfo<uint32_t>::getTombstoneKey()}; + } + + static unsigned getHashValue(const ID &Val) { + return DenseMapInfo<uint32_t>::getHashValue(Val.Value); + } + + static bool isEqual(const ID &LHS, const ID &RHS) { return LHS == RHS; } +}; +} // namespace llvm + +#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h deleted file mode 100644 index 03636be..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h +++ /dev/null @@ -1,35 +0,0 @@ -//===- Checker.h - C++ Lifetime Safety Analysis -*----------- C++-*-=========// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines and enforces the lifetime safety policy. It detects -// use-after-free errors by examining loan expiration points and checking if -// any live origins hold the expired loans. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H - -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" - -namespace clang::lifetimes::internal { - -/// Runs the lifetime checker, which detects use-after-free errors by -/// examining loan expiration points and checking if any live origins hold -/// the expired loan. -void runLifetimeChecker(const LoanPropagationAnalysis &LoanPropagation, - const LiveOriginsAnalysis &LiveOrigins, - const FactManager &FactMgr, AnalysisDeclContext &ADC, - LifetimeSafetyReporter *Reporter); - -} // namespace clang::lifetimes::internal - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h deleted file mode 100644 index 6a90aeb..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ /dev/null @@ -1,232 +0,0 @@ -//===- Facts.h - Lifetime Analysis Facts and Fact Manager ------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines Facts, which are atomic lifetime-relevant events (such as -// loan issuance, loan expiration, origin flow, and use), and the FactManager, -// which manages the storage and retrieval of facts for each CFG block. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H - -#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" -#include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Analysis/CFG.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Debug.h" -#include <cstdint> - -namespace clang::lifetimes::internal { -/// An abstract base class for a single, atomic lifetime-relevant event. -class Fact { - -public: - enum class Kind : uint8_t { - /// A new loan is issued from a borrow expression (e.g., &x). - Issue, - /// A loan expires as its underlying storage is freed (e.g., variable goes - /// out of scope). - Expire, - /// An origin is propagated from a source to a destination (e.g., p = q). - /// This can also optionally kill the destination origin before flowing into - /// it. Otherwise, the source's loan set is merged into the destination's - /// loan set. - OriginFlow, - /// An origin escapes the function by flowing into the return value. - ReturnOfOrigin, - /// An origin is used (eg. appears as l-value expression like DeclRefExpr). - Use, - /// A marker for a specific point in the code, for testing. - TestPoint, - }; - -private: - Kind K; - -protected: - Fact(Kind K) : K(K) {} - -public: - virtual ~Fact() = default; - Kind getKind() const { return K; } - - template <typename T> const T *getAs() const { - if (T::classof(this)) - return static_cast<const T *>(this); - return nullptr; - } - - virtual void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const; -}; - -/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific -/// `Fact`. identified by a lifetime-related event (`Fact`). -/// -/// A `ProgramPoint` has "after" semantics: it represents the location -/// immediately after its corresponding `Fact`. -using ProgramPoint = const Fact *; - -class IssueFact : public Fact { - LoanID LID; - OriginID OID; - -public: - static bool classof(const Fact *F) { return F->getKind() == Kind::Issue; } - - IssueFact(LoanID LID, OriginID OID) : Fact(Kind::Issue), LID(LID), OID(OID) {} - LoanID getLoanID() const { return LID; } - OriginID getOriginID() const { return OID; } - void dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &OM) const override; -}; - -class ExpireFact : public Fact { - LoanID LID; - SourceLocation ExpiryLoc; - -public: - static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; } - - ExpireFact(LoanID LID, SourceLocation ExpiryLoc) - : Fact(Kind::Expire), LID(LID), ExpiryLoc(ExpiryLoc) {} - - LoanID getLoanID() const { return LID; } - SourceLocation getExpiryLoc() const { return ExpiryLoc; } - - void dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &) 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. - bool KillDest; - -public: - static bool classof(const Fact *F) { - return F->getKind() == Kind::OriginFlow; - } - - OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest) - : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc), - KillDest(KillDest) {} - - OriginID getDestOriginID() const { return OIDDest; } - OriginID getSrcOriginID() const { return OIDSrc; } - bool getKillDest() const { return KillDest; } - - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; -}; - -class ReturnOfOriginFact : public Fact { - OriginID OID; - -public: - static bool classof(const Fact *F) { - return F->getKind() == Kind::ReturnOfOrigin; - } - - ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {} - OriginID getReturnedOriginID() const { return OID; } - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; -}; - -class UseFact : public Fact { - const Expr *UseExpr; - // True if this use is a write operation (e.g., left-hand side of assignment). - // Write operations are exempted from use-after-free checks. - bool IsWritten = false; - -public: - static bool classof(const Fact *F) { return F->getKind() == Kind::Use; } - - UseFact(const Expr *UseExpr) : Fact(Kind::Use), UseExpr(UseExpr) {} - - OriginID getUsedOrigin(const OriginManager &OM) const { - // TODO: Remove const cast and make OriginManager::get as const. - return const_cast<OriginManager &>(OM).get(*UseExpr); - } - const Expr *getUseExpr() const { return UseExpr; } - void markAsWritten() { IsWritten = true; } - bool isWritten() const { return IsWritten; } - - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const override; -}; - -/// A dummy-fact used to mark a specific point in the code for testing. -/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast. -class TestPointFact : public Fact { - StringRef Annotation; - -public: - static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; } - - explicit TestPointFact(StringRef Annotation) - : Fact(Kind::TestPoint), Annotation(Annotation) {} - - StringRef getAnnotation() const { return Annotation; } - - void dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const override; -}; - -class FactManager { -public: - llvm::ArrayRef<const Fact *> getFacts(const CFGBlock *B) const { - auto It = BlockToFactsMap.find(B); - if (It != BlockToFactsMap.end()) - return It->second; - return {}; - } - - void addBlockFacts(const CFGBlock *B, llvm::ArrayRef<Fact *> NewFacts) { - if (!NewFacts.empty()) - BlockToFactsMap[B].assign(NewFacts.begin(), NewFacts.end()); - } - - template <typename FactType, typename... Args> - FactType *createFact(Args &&...args) { - void *Mem = FactAllocator.Allocate<FactType>(); - return new (Mem) FactType(std::forward<Args>(args)...); - } - - void dump(const CFG &Cfg, AnalysisDeclContext &AC) const; - - /// Retrieves program points that were specially marked in the source code - /// for testing. - /// - /// The analysis recognizes special function calls of the form - /// `void("__lifetime_test_point_<name>")` as test points. This method returns - /// a map from the annotation string (<name>) to the corresponding - /// `ProgramPoint`. This allows test harnesses to query the analysis state at - /// user-defined locations in the code. - /// \note This is intended for testing only. - llvm::StringMap<ProgramPoint> getTestPoints() const; - - LoanManager &getLoanMgr() { return LoanMgr; } - const LoanManager &getLoanMgr() const { return LoanMgr; } - OriginManager &getOriginMgr() { return OriginMgr; } - const OriginManager &getOriginMgr() const { return OriginMgr; } - -private: - LoanManager LoanMgr; - OriginManager OriginMgr; - llvm::DenseMap<const clang::CFGBlock *, llvm::SmallVector<const Fact *>> - BlockToFactsMap; - llvm::BumpPtrAllocator FactAllocator; -}; -} // namespace clang::lifetimes::internal - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h deleted file mode 100644 index 5e58abe..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ /dev/null @@ -1,106 +0,0 @@ -//===- FactsGenerator.h - Lifetime Facts Generation -------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the FactsGenerator, which traverses the AST to generate -// lifetime-relevant facts (such as loan issuance, expiration, origin flow, -// and use) from CFG statements. These facts are used by the dataflow analyses -// to track pointer lifetimes and detect use-after-free errors. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTSGENERATOR_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTSGENERATOR_H - -#include "clang/AST/StmtVisitor.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" -#include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Analysis/CFG.h" -#include "llvm/ADT/SmallVector.h" - -namespace clang::lifetimes::internal { - -class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { - using Base = ConstStmtVisitor<FactsGenerator>; - -public: - FactsGenerator(FactManager &FactMgr, AnalysisDeclContext &AC) - : FactMgr(FactMgr), AC(AC) {} - - void run(); - - void VisitDeclStmt(const DeclStmt *DS); - void VisitDeclRefExpr(const DeclRefExpr *DRE); - void VisitCXXConstructExpr(const CXXConstructExpr *CCE); - void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE); - void VisitCallExpr(const CallExpr *CE); - void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N); - void VisitImplicitCastExpr(const ImplicitCastExpr *ICE); - void VisitUnaryOperator(const UnaryOperator *UO); - void VisitReturnStmt(const ReturnStmt *RS); - void VisitBinaryOperator(const BinaryOperator *BO); - void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE); - void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE); - void VisitInitListExpr(const InitListExpr *ILE); - void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE); - -private: - void handleDestructor(const CFGAutomaticObjDtor &DtorOpt); - - void handleGSLPointerConstruction(const CXXConstructExpr *CCE); - - /// Checks if a call-like expression creates a borrow by passing a value to a - /// reference parameter, creating an IssueFact if it does. - /// \param IsGslConstruction True if this is a GSL construction where all - /// argument origins should flow to the returned origin. - void handleFunctionCall(const Expr *Call, const FunctionDecl *FD, - ArrayRef<const Expr *> Args, - bool IsGslConstruction = false); - - template <typename Destination, typename Source> - void flowOrigin(const Destination &D, const Source &S) { - OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D); - OriginID SrcOID = FactMgr.getOriginMgr().get(S); - CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( - DestOID, SrcOID, /*KillDest=*/false)); - } - - template <typename Destination, typename Source> - void killAndFlowOrigin(const Destination &D, const Source &S) { - OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D); - OriginID SrcOID = FactMgr.getOriginMgr().get(S); - CurrentBlockFacts.push_back( - FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID, /*KillDest=*/true)); - } - - /// Checks if the expression is a `void("__lifetime_test_point_...")` cast. - /// If so, creates a `TestPointFact` and returns true. - bool handleTestPoint(const CXXFunctionalCastExpr *FCE); - - void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr); - - // 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 handleUse(const DeclRefExpr *DRE); - - void markUseAsWrite(const DeclRefExpr *DRE); - - FactManager &FactMgr; - AnalysisDeclContext &AC; - llvm::SmallVector<Fact *> CurrentBlockFacts; - // To distinguish between reads and writes for use-after-free checks, this map - // stores the `UseFact` for each `DeclRefExpr`. We initially identify all - // `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; -}; - -} // namespace clang::lifetimes::internal - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTSGENERATOR_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h deleted file mode 100644 index 91ffbb1..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ /dev/null @@ -1,87 +0,0 @@ -//===- LifetimeSafety.h - C++ Lifetime Safety Analysis -*----------- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the main entry point and orchestrator for the C++ Lifetime -// Safety Analysis. It coordinates the entire analysis pipeline: fact -// generation, loan propagation, live origins analysis, and enforcement of -// lifetime safety policy. -// -// The analysis is based on the concepts of "origins" and "loans" to track -// pointer lifetimes and detect issues like use-after-free and dangling -// pointers. See the RFC for more details: -// https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291 -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H - -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" -#include "clang/Analysis/AnalysisDeclContext.h" - -namespace clang::lifetimes { - -/// Enum to track the confidence level of a potential error. -enum class Confidence : uint8_t { - None, - Maybe, // Reported as a potential error (-Wlifetime-safety-strict) - Definite // Reported as a definite error (-Wlifetime-safety-permissive) -}; - -class LifetimeSafetyReporter { -public: - LifetimeSafetyReporter() = default; - virtual ~LifetimeSafetyReporter() = default; - - virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, - SourceLocation FreeLoc, - Confidence Confidence) {} -}; - -/// The main entry point for the analysis. -void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, - LifetimeSafetyReporter *Reporter); - -namespace internal { -/// An object to hold the factories for immutable collections, ensuring -/// that all created states share the same underlying memory management. -struct LifetimeFactory { - OriginLoanMap::Factory OriginMapFactory{/*canonicalize=*/false}; - LoanSet::Factory LoanSetFactory{/*canonicalize=*/false}; - LivenessMap::Factory LivenessMapFactory{/*canonicalize=*/false}; -}; - -/// Running the lifetime safety analysis and querying its results. It -/// encapsulates the various dataflow analyses. -class LifetimeSafetyAnalysis { -public: - LifetimeSafetyAnalysis(AnalysisDeclContext &AC, - LifetimeSafetyReporter *Reporter); - - void run(); - - /// \note These are provided only for testing purposes. - LoanPropagationAnalysis &getLoanPropagation() const { - return *LoanPropagation; - } - LiveOriginsAnalysis &getLiveOrigins() const { return *LiveOrigins; } - FactManager &getFactManager() { return FactMgr; } - -private: - AnalysisDeclContext &AC; - LifetimeSafetyReporter *Reporter; - LifetimeFactory Factory; - FactManager FactMgr; - std::unique_ptr<LiveOriginsAnalysis> LiveOrigins; - std::unique_ptr<LoanPropagationAnalysis> LoanPropagation; -}; -} // namespace internal -} // namespace clang::lifetimes - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h deleted file mode 100644 index c4f5f0e..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h +++ /dev/null @@ -1,97 +0,0 @@ -//===- LiveOrigins.h - Live Origins Analysis -------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the LiveOriginAnalysis, a backward dataflow analysis that -// determines which origins are "live" at each program point. An origin is -// "live" at a program point if there's a potential future use of a pointer it -// is associated with. Liveness is "generated" by a use of an origin (e.g., a -// `UseFact` from a read of a pointer) and is "killed" (i.e., it stops being -// live) when the origin is replaced by flowing a different origin into it -// (e.g., an OriginFlow from an assignment that kills the destination). -// -// This information is used for detecting use-after-free errors, as it allows us -// to check if a live origin holds a loan to an object that has already expired. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LIVE_ORIGINS_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LIVE_ORIGINS_H - -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" -#include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Analysis/CFG.h" -#include "llvm/ADT/FoldingSet.h" -#include "llvm/ADT/ImmutableMap.h" -#include "llvm/Support/Debug.h" - -namespace clang::lifetimes::internal { - -enum class LivenessKind : uint8_t { - Dead, // Not alive - Maybe, // Live on some path but not all paths (may-be-live) - Must // Live on all paths (must-be-live) -}; - -/// Information about why an origin is live at a program point. -struct LivenessInfo { - /// The use that makes the origin live. If liveness is propagated from - /// multiple uses along different paths, this will point to the use appearing - /// earlier in the translation unit. - /// This is 'null' when the origin is not live. - const UseFact *CausingUseFact; - - /// The kind of liveness of the origin. - /// `Must`: The origin is live on all control-flow paths from the current - /// point to the function's exit (i.e. the current point is dominated by a set - /// of uses). - /// `Maybe`: indicates it is live on some but not all paths. - /// - /// This determines the diagnostic's confidence level. - /// `Must`-be-alive at expiration implies a definite use-after-free, - /// while `Maybe`-be-alive suggests a potential one on some paths. - LivenessKind Kind; - - LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {} - LivenessInfo(const UseFact *UF, LivenessKind K) - : CausingUseFact(UF), Kind(K) {} - - bool operator==(const LivenessInfo &Other) const { - return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind; - } - bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); } - - void Profile(llvm::FoldingSetNodeID &IDBuilder) const { - IDBuilder.AddPointer(CausingUseFact); - IDBuilder.Add(Kind); - } -}; - -using LivenessMap = llvm::ImmutableMap<OriginID, LivenessInfo>; - -class LiveOriginsAnalysis { -public: - LiveOriginsAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - LivenessMap::Factory &SF); - ~LiveOriginsAnalysis(); - - /// Returns the set of origins that are live at a specific program point, - /// along with the the details of the liveness. - LivenessMap getLiveOriginsAt(ProgramPoint P) const; - - // Dump liveness values on all test points in the program. - void dump(llvm::raw_ostream &OS, - llvm::StringMap<ProgramPoint> TestPoints) const; - -private: - class Impl; - std::unique_ptr<Impl> PImpl; -}; - -} // namespace clang::lifetimes::internal - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LIVE_ORIGINS_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h deleted file mode 100644 index 447d05c..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h +++ /dev/null @@ -1,48 +0,0 @@ -//===- LoanPropagation.h - Loan Propagation Analysis -----------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the LoanPropagationAnalysis, a forward dataflow analysis -// that tracks which loans each origin holds at each program point. Loans -// represent borrows of storage locations and are propagated through the -// program as pointers are copied or assigned. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H - -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Analysis/CFG.h" -#include "llvm/ADT/ImmutableMap.h" -#include "llvm/ADT/ImmutableSet.h" - -namespace clang::lifetimes::internal { - -// Using LLVM's immutable collections is efficient for dataflow analysis -// as it avoids deep copies during state transitions. -// TODO(opt): Consider using a bitset to represent the set of loans. -using LoanSet = llvm::ImmutableSet<LoanID>; -using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>; - -class LoanPropagationAnalysis { -public: - LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - OriginLoanMap::Factory &OriginLoanMapFactory, - LoanSet::Factory &LoanSetFactory); - ~LoanPropagationAnalysis(); - - LoanSet getLoans(OriginID OID, ProgramPoint P) const; - -private: - class Impl; - std::unique_ptr<Impl> PImpl; -}; - -} // namespace clang::lifetimes::internal - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h deleted file mode 100644 index 7f5cf03..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h +++ /dev/null @@ -1,80 +0,0 @@ -//===- Loans.h - Loan and Access Path Definitions --------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines the Loan and AccessPath structures, which represent -// borrows of storage locations, and the LoanManager, which manages the -// creation and retrieval of loans during lifetime analysis. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H - -#include "clang/AST/Decl.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" -#include "llvm/Support/raw_ostream.h" - -namespace clang::lifetimes::internal { - -using LoanID = utils::ID<struct LoanTag>; -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. -/// TODO: Model access paths of other types, e.g., s.field, heap and globals. -struct AccessPath { - const clang::ValueDecl *D; - - AccessPath(const clang::ValueDecl *D) : D(D) {} -}; - -/// Information about a single borrow, or "Loan". A loan is created when a -/// reference or pointer is created. -struct Loan { - /// TODO: Represent opaque loans. - /// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it - /// is represented as empty LoanSet - LoanID ID; - AccessPath Path; - /// The expression that creates the loan, e.g., &x. - const Expr *IssueExpr; - - Loan(LoanID id, AccessPath path, const Expr *IssueExpr) - : ID(id), Path(path), IssueExpr(IssueExpr) {} - - void dump(llvm::raw_ostream &OS) const; -}; - -/// Manages the creation, storage and retrieval of loans. -class LoanManager { -public: - LoanManager() = default; - - Loan &addLoan(AccessPath Path, const Expr *IssueExpr) { - AllLoans.emplace_back(getNextLoanID(), Path, IssueExpr); - return AllLoans.back(); - } - - const Loan &getLoan(LoanID ID) const { - assert(ID.Value < AllLoans.size()); - return AllLoans[ID.Value]; - } - llvm::ArrayRef<Loan> getLoans() const { return AllLoans; } - -private: - LoanID getNextLoanID() { return NextLoanID++; } - - LoanID NextLoanID{0}; - /// TODO(opt): Profile and evaluate the usefullness of small buffer - /// optimisation. - llvm::SmallVector<Loan> AllLoans; -}; -} // namespace clang::lifetimes::internal - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h deleted file mode 100644 index ba138b0..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h +++ /dev/null @@ -1,91 +0,0 @@ -//===- Origins.h - Origin and Origin Management ----------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines Origins, which represent the set of possible loans a -// pointer-like object could hold, and the OriginManager, which manages the -// creation, storage, and retrieval of origins for variables and expressions. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_ORIGINS_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_ORIGINS_H - -#include "clang/AST/Decl.h" -#include "clang/AST/Expr.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" - -namespace clang::lifetimes::internal { - -using OriginID = utils::ID<struct OriginTag>; - -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) { - return OS << ID.Value; -} - -/// An Origin is a symbolic identifier that represents the set of possible -/// loans a pointer-like object could hold at any given time. -/// TODO: Enhance the origin model to handle complex types, pointer -/// indirection and reborrowing. The plan is to move from a single origin per -/// variable/expression to a "list of origins" governed by the Type. -/// For example, the type 'int**' would have two origins. -/// See discussion: -/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238 -struct Origin { - OriginID ID; - /// A pointer to the AST node that this origin represents. This union - /// distinguishes between origins from declarations (variables or parameters) - /// and origins from expressions. - llvm::PointerUnion<const clang::ValueDecl *, const clang::Expr *> Ptr; - - Origin(OriginID ID, const clang::ValueDecl *D) : ID(ID), Ptr(D) {} - Origin(OriginID ID, const clang::Expr *E) : ID(ID), Ptr(E) {} - - const clang::ValueDecl *getDecl() const { - return Ptr.dyn_cast<const clang::ValueDecl *>(); - } - const clang::Expr *getExpr() const { - return Ptr.dyn_cast<const clang::Expr *>(); - } -}; - -/// Manages the creation, storage, and retrieval of origins for pointer-like -/// variables and expressions. -class OriginManager { -public: - OriginManager() = default; - - Origin &addOrigin(OriginID ID, const clang::ValueDecl &D); - Origin &addOrigin(OriginID ID, const clang::Expr &E); - - // TODO: Mark this method as const once we remove the call to getOrCreate. - OriginID get(const Expr &E); - - OriginID get(const ValueDecl &D); - - OriginID getOrCreate(const Expr &E); - - const Origin &getOrigin(OriginID ID) const; - - llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; } - - OriginID getOrCreate(const ValueDecl &D); - - void dump(OriginID OID, llvm::raw_ostream &OS) const; - -private: - OriginID getNextOriginID() { return NextOriginID++; } - - OriginID NextOriginID{0}; - /// TODO(opt): Profile and evaluate the usefullness of small buffer - /// optimisation. - llvm::SmallVector<Origin> AllOrigins; - llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID; - llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID; -}; -} // namespace clang::lifetimes::internal - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_ORIGINS_H diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Utils.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Utils.h deleted file mode 100644 index 4183cab..0000000 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Utils.h +++ /dev/null @@ -1,118 +0,0 @@ -//===- Utils.h - Utility Functions for Lifetime Safety --------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// This file provides utilities for the lifetime safety analysis, including -// join operations for LLVM's immutable data structures. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_UTILS_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_UTILS_H - -#include "llvm/ADT/ImmutableMap.h" -#include "llvm/ADT/ImmutableSet.h" - -namespace clang::lifetimes::internal::utils { - -/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type. -/// Used for giving ID to loans and origins. -template <typename Tag> struct ID { - uint32_t Value = 0; - - bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; } - bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); } - bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; } - ID<Tag> operator++(int) { - ID<Tag> Tmp = *this; - ++Value; - return Tmp; - } - void Profile(llvm::FoldingSetNodeID &IDBuilder) const { - IDBuilder.AddInteger(Value); - } -}; - -/// Computes the union of two ImmutableSets. -template <typename T> -static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, - llvm::ImmutableSet<T> B, - typename llvm::ImmutableSet<T>::Factory &F) { - if (A.getHeight() < B.getHeight()) - std::swap(A, B); - for (const T &E : B) - A = F.add(A, E); - return A; -} - -/// Describes the strategy for joining two `ImmutableMap` instances, primarily -/// differing in how they handle keys that are unique to one of the maps. -/// -/// A `Symmetric` join is universally correct, while an `Asymmetric` join -/// serves as a performance optimization. The latter is applicable only when the -/// join operation possesses a left identity element, allowing for a more -/// efficient, one-sided merge. -enum class JoinKind { - /// A symmetric join applies the `JoinValues` operation to keys unique to - /// either map, ensuring that values from both maps contribute to the result. - Symmetric, - /// An asymmetric join preserves keys unique to the first map as-is, while - /// applying the `JoinValues` operation only to keys unique to the second map. - Asymmetric, -}; - -/// Computes the key-wise union of two ImmutableMaps. -// TODO(opt): This key-wise join is a performance bottleneck. A more -// efficient merge could be implemented using a Patricia Trie or HAMT -// instead of the current AVL-tree-based ImmutableMap. -template <typename K, typename V, typename Joiner> -static llvm::ImmutableMap<K, V> -join(const llvm::ImmutableMap<K, V> &A, const llvm::ImmutableMap<K, V> &B, - typename llvm::ImmutableMap<K, V>::Factory &F, Joiner JoinValues, - JoinKind Kind) { - if (A.getHeight() < B.getHeight()) - return join(B, A, F, JoinValues, Kind); - - // For each element in B, join it with the corresponding element in A - // (or with an empty value if it doesn't exist in A). - llvm::ImmutableMap<K, V> Res = A; - for (const auto &Entry : B) { - const K &Key = Entry.first; - const V &ValB = Entry.second; - Res = F.add(Res, Key, JoinValues(A.lookup(Key), &ValB)); - } - if (Kind == JoinKind::Symmetric) { - for (const auto &Entry : A) { - const K &Key = Entry.first; - const V &ValA = Entry.second; - if (!B.contains(Key)) - Res = F.add(Res, Key, JoinValues(&ValA, nullptr)); - } - } - return Res; -} -} // namespace clang::lifetimes::internal::utils - -namespace llvm { -template <typename Tag> -struct DenseMapInfo<clang::lifetimes::internal::utils::ID<Tag>> { - using ID = clang::lifetimes::internal::utils::ID<Tag>; - - static inline ID getEmptyKey() { - return {DenseMapInfo<uint32_t>::getEmptyKey()}; - } - - static inline ID getTombstoneKey() { - return {DenseMapInfo<uint32_t>::getTombstoneKey()}; - } - - static unsigned getHashValue(const ID &Val) { - return DenseMapInfo<uint32_t>::getHashValue(Val.Value); - } - - static bool isEqual(const ID &LHS, const ID &RHS) { return LHS == RHS; } -}; -} // namespace llvm - -#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_UTILS_H diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 4b27a42..ef3f59f 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1400,7 +1400,7 @@ def C23 : DiagGroup<"c23-extensions", [VariadicMacroArgumentsOmitted]>; def : DiagGroup<"c2x-extensions", [C23]>; // A warning group for warnings about using C2y features as extensions. -def C2y : DiagGroup<"c2y-extensions">; +def C2y : DiagGroup<"c2y-extensions", [StaticInInline]>; // Previously supported warning group which is no longer pertinent as binary // literals are a C++14 and C23 extension now instead of a GNU extension. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5be63c0..3df28f2 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6326,11 +6326,14 @@ def warn_internal_linkage_local_storage : Warning< InGroup<IgnoredAttributes>; def ext_internal_in_extern_inline : ExtWarn< - "static %select{function|variable}0 %1 is used in an inline function with " - "external linkage">, InGroup<StaticInInline>; + "using static %select{function|variable}0 %1 in an inline function with " + "external linkage is a C2y extension">, InGroup<StaticInInline>; def ext_internal_in_extern_inline_quiet : Extension< - "static %select{function|variable}0 %1 is used in an inline function with " - "external linkage">, InGroup<StaticInInline>; + ext_internal_in_extern_inline.Summary>, InGroup<StaticInInline>; +def warn_c2y_compat_internal_in_extern_inline : Warning< + "using static %select{function|variable}0 %1 in an inline function with " + "external linkage is incompatible with standards before C2y">, + InGroup<CPre2yCompat>, DefaultIgnore; def warn_static_local_in_extern_inline : Warning< "non-constant static local variable in inline function may be different " "in different files">, InGroup<StaticLocalInInline>; diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 11ad61f..b4c24d7 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -451,6 +451,11 @@ public: return createBitcast(src, getPointerTo(newPointeeTy)); } + mlir::Value createPtrIsNull(mlir::Value ptr) { + mlir::Value nullPtr = getNullPtr(ptr.getType(), ptr.getLoc()); + return createCompare(ptr.getLoc(), cir::CmpOpKind::eq, ptr, nullPtr); + } + //===--------------------------------------------------------------------===// // Binary Operators //===--------------------------------------------------------------------===// @@ -644,6 +649,12 @@ public: return getI64IntegerAttr(size.getQuantity()); } + // Creates constant nullptr for pointer type ty. + cir::ConstantOp getNullPtr(mlir::Type ty, mlir::Location loc) { + assert(!cir::MissingFeatures::targetCodeGenInfoGetNullPointer()); + return cir::ConstantOp::create(*this, loc, getConstPtrAttr(ty, 0)); + } + /// Create a loop condition. cir::ConditionOp createCondition(mlir::Value condition) { return cir::ConditionOp::create(*this, condition.getLoc(), condition); diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index ace2086..df82ca1 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -85,6 +85,7 @@ struct MissingFeatures { static bool opFuncReadOnly() { return false; } static bool opFuncSection() { return false; } static bool opFuncWillReturn() { return false; } + static bool opFuncNoReturn() { return false; } static bool setLLVMFunctionFEnvAttributes() { return false; } static bool setFunctionAttributes() { return false; } @@ -256,6 +257,8 @@ struct MissingFeatures { static bool loopInfoStack() { return false; } static bool lowerAggregateLoadStore() { return false; } static bool lowerModeOptLevel() { return false; } + static bool loweringPrepareX86CXXABI() { return false; } + static bool loweringPrepareAArch64XXABI() { return false; } static bool maybeHandleStaticInExternC() { return false; } static bool mergeAllConstants() { return false; } static bool metaDataNode() { return false; } diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index ec38231..c2f2ac5 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -545,15 +545,16 @@ multiclass BoolFOption<string flag_base, KeyPathAndMacro kpm, Group<f_Group>; } -// Creates a BoolOption where both of the flags are prefixed with "g" and have -// the Group<g_Group>. +// Creates a BoolOption where both of the flags are prefixed with "g". +// Does *not* map to g_Group, because that is reserved for flags that are +// intended to enable (or disable) debug info, which is not appropriate for a +// negative boolean flag (-gno-${feature}). // Used for -cc1 frontend options. Driver-only options do not map to // CompilerInvocation. multiclass BoolGOption<string flag_base, KeyPathAndMacro kpm, Default default, FlagDef flag1, FlagDef flag2, BothFlags both = BothFlags<[]>> { - defm NAME : BoolOption<"g", flag_base, kpm, default, flag1, flag2, both>, - Group<g_Group>; + defm NAME : BoolOption<"g", flag_base, kpm, default, flag1, flag2, both>; } multiclass BoolMOption<string flag_base, KeyPathAndMacro kpm, @@ -4845,8 +4846,7 @@ defm structor_decl_linkage_names NegFlag<SetFalse>, PosFlag<SetTrue, [], [], "Attach linkage names to C++ constructor/destructor " - "declarations in DWARF." - "Implies -g.">, + "declarations in DWARF.">, BothFlags<[], [ClangOption, CLOption, CC1Option]>>, DocBrief<[{On some ABIs (e.g., Itanium), constructors and destructors may have multiple variants. Historically, when generating DWARF, Clang did not attach ``DW_AT_linkage_name`` to structor DIEs because there were multiple possible manglings (depending on the structor variant) that could be used. With ``-gstructor-decl-linkage-names``, for ABIs with structor variants, we attach a "unified" mangled name to structor declarations DIEs which debuggers can use to look up all the definitions for a structor declaration. E.g., a "unified" mangled name ``_ZN3FooC4Ev`` may have multiple definitions associated with it such as ``_ZN3FooC1Ev`` and ``_ZN3FooC2Ev``. @@ -4855,7 +4855,7 @@ defm key_instructions : BoolGOption<"key-instructions", CodeGenOpts<"DebugKeyInstructions">, DefaultFalse, NegFlag<SetFalse>, PosFlag<SetTrue, [], [], "Enable Key Instructions, which reduces the jumpiness of debug stepping in optimized C/C++ code" - " in some debuggers. DWARF only. Implies -g.">, + " in some debuggers. DWARF only.">, BothFlags<[], [ClangOption, CLOption, CC1Option]>>; def headerpad__max__install__names : Joined<["-"], "headerpad_max_install_names">; def help : Flag<["-", "--"], "help">, diff --git a/clang/include/clang/Sema/SemaOpenACC.h b/clang/include/clang/Sema/SemaOpenACC.h index 09fdf75..6cadc34 100644 --- a/clang/include/clang/Sema/SemaOpenACC.h +++ b/clang/include/clang/Sema/SemaOpenACC.h @@ -911,6 +911,7 @@ public: ExprResult CheckReductionVar(OpenACCDirectiveKind DirectiveKind, OpenACCReductionOperator ReductionOp, Expr *VarExpr); + bool CheckReductionVarType(Expr *VarExpr); /// Called to check the 'var' type is a variable of pointer type, necessary /// for 'deviceptr' and 'attach' clauses. Returns true on success. diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index f899b3c..597cbd8 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -5290,6 +5290,33 @@ QualType ArraySectionExpr::getBaseOriginalType(const Expr *Base) { return OriginalTy; } +QualType ArraySectionExpr::getElementType() const { + QualType BaseTy = getBase()->IgnoreParenImpCasts()->getType(); + // We only have to look into the array section exprs, else we will get the + // type of the base, which should already be valid. + if (auto *ASE = dyn_cast<ArraySectionExpr>(getBase()->IgnoreParenImpCasts())) + BaseTy = ASE->getElementType(); + + if (BaseTy->isAnyPointerType()) + return BaseTy->getPointeeType(); + if (BaseTy->isArrayType()) + return BaseTy->castAsArrayTypeUnsafe()->getElementType(); + + // If this isn't a pointer or array, the base is a dependent expression, so + // just return the BaseTy anyway. + assert(BaseTy->isInstantiationDependentType()); + return BaseTy; +} + +QualType ArraySectionExpr::getBaseType() const { + // We only have to look into the array section exprs, else we will get the + // type of the base, which should already be valid. + if (auto *ASE = dyn_cast<ArraySectionExpr>(getBase()->IgnoreParenImpCasts())) + return ASE->getElementType(); + + return getBase()->IgnoreParenImpCasts()->getType(); +} + RecoveryExpr::RecoveryExpr(ASTContext &Ctx, QualType T, SourceLocation BeginLoc, SourceLocation EndLoc, ArrayRef<Expr *> SubExprs) : Expr(RecoveryExprClass, T.getNonReferenceType(), diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt index 1dbd415..5a26f3e 100644 --- a/clang/lib/Analysis/CMakeLists.txt +++ b/clang/lib/Analysis/CMakeLists.txt @@ -21,6 +21,8 @@ add_clang_library(clangAnalysis FixitUtil.cpp IntervalPartition.cpp IssueHash.cpp + LifetimeAnnotations.cpp + LifetimeSafety.cpp LiveVariables.cpp MacroExpansionContext.cpp ObjCNoReturn.cpp @@ -49,4 +51,3 @@ add_clang_library(clangAnalysis add_subdirectory(plugins) add_subdirectory(FlowSensitive) -add_subdirectory(LifetimeSafety) diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeAnnotations.cpp index ad61a42..e791224 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeAnnotations.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" +#include "clang/Analysis/Analyses/LifetimeAnnotations.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" @@ -13,7 +13,8 @@ #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" -namespace clang::lifetimes { +namespace clang { +namespace lifetimes { const FunctionDecl * getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) { @@ -70,4 +71,5 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) { return isNormalAssignmentOperator(FD); } -} // namespace clang::lifetimes +} // namespace lifetimes +} // namespace clang diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp new file mode 100644 index 0000000..6196ec3 --- /dev/null +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -0,0 +1,1546 @@ +//===- LifetimeSafety.cpp - C++ Lifetime Safety Analysis -*--------- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "clang/Analysis/Analyses/LifetimeSafety.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/AST/Type.h" +#include "clang/Analysis/Analyses/LifetimeAnnotations.h" +#include "clang/Analysis/Analyses/PostOrderCFGView.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/FlowSensitive/DataflowWorklist.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/ImmutableSet.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/TimeProfiler.h" +#include <cstdint> +#include <memory> +#include <optional> + +namespace clang::lifetimes { +namespace internal { + +/// Represents the storage location being borrowed, e.g., a specific stack +/// variable. +/// TODO: Model access paths of other types, e.g., s.field, heap and globals. +struct AccessPath { + const clang::ValueDecl *D; + + AccessPath(const clang::ValueDecl *D) : D(D) {} +}; + +/// Information about a single borrow, or "Loan". A loan is created when a +/// reference or pointer is created. +struct Loan { + /// TODO: Represent opaque loans. + /// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it + /// is represented as empty LoanSet + LoanID ID; + AccessPath Path; + /// The expression that creates the loan, e.g., &x. + const Expr *IssueExpr; + + Loan(LoanID id, AccessPath path, const Expr *IssueExpr) + : ID(id), Path(path), IssueExpr(IssueExpr) {} + + void dump(llvm::raw_ostream &OS) const { + OS << ID << " (Path: "; + OS << Path.D->getNameAsString() << ")"; + } +}; + +/// An Origin is a symbolic identifier that represents the set of possible +/// loans a pointer-like object could hold at any given time. +/// TODO: Enhance the origin model to handle complex types, pointer +/// indirection and reborrowing. The plan is to move from a single origin per +/// variable/expression to a "list of origins" governed by the Type. +/// For example, the type 'int**' would have two origins. +/// See discussion: +/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238 +struct Origin { + OriginID ID; + /// A pointer to the AST node that this origin represents. This union + /// distinguishes between origins from declarations (variables or parameters) + /// and origins from expressions. + llvm::PointerUnion<const clang::ValueDecl *, const clang::Expr *> Ptr; + + Origin(OriginID ID, const clang::ValueDecl *D) : ID(ID), Ptr(D) {} + Origin(OriginID ID, const clang::Expr *E) : ID(ID), Ptr(E) {} + + const clang::ValueDecl *getDecl() const { + return Ptr.dyn_cast<const clang::ValueDecl *>(); + } + const clang::Expr *getExpr() const { + return Ptr.dyn_cast<const clang::Expr *>(); + } +}; + +/// Manages the creation, storage and retrieval of loans. +class LoanManager { +public: + LoanManager() = default; + + Loan &addLoan(AccessPath Path, const Expr *IssueExpr) { + AllLoans.emplace_back(getNextLoanID(), Path, IssueExpr); + return AllLoans.back(); + } + + const Loan &getLoan(LoanID ID) const { + assert(ID.Value < AllLoans.size()); + return AllLoans[ID.Value]; + } + llvm::ArrayRef<Loan> getLoans() const { return AllLoans; } + +private: + LoanID getNextLoanID() { return NextLoanID++; } + + LoanID NextLoanID{0}; + /// TODO(opt): Profile and evaluate the usefullness of small buffer + /// optimisation. + llvm::SmallVector<Loan> AllLoans; +}; + +/// Manages the creation, storage, and retrieval of origins for pointer-like +/// variables and expressions. +class OriginManager { +public: + OriginManager() = default; + + Origin &addOrigin(OriginID ID, const clang::ValueDecl &D) { + AllOrigins.emplace_back(ID, &D); + return AllOrigins.back(); + } + Origin &addOrigin(OriginID ID, const clang::Expr &E) { + AllOrigins.emplace_back(ID, &E); + return AllOrigins.back(); + } + + // TODO: Mark this method as const once we remove the call to getOrCreate. + OriginID get(const Expr &E) { + auto It = ExprToOriginID.find(&E); + if (It != ExprToOriginID.end()) + return It->second; + // If the expression itself has no specific origin, and it's a reference + // to a declaration, its origin is that of the declaration it refers to. + // For pointer types, where we don't pre-emptively create an origin for the + // DeclRefExpr itself. + if (const auto *DRE = dyn_cast<DeclRefExpr>(&E)) + return get(*DRE->getDecl()); + // TODO: This should be an assert(It != ExprToOriginID.end()). The current + // implementation falls back to getOrCreate to avoid crashing on + // yet-unhandled pointer expressions, creating an empty origin for them. + return getOrCreate(E); + } + + OriginID get(const ValueDecl &D) { + auto It = DeclToOriginID.find(&D); + // TODO: This should be an assert(It != DeclToOriginID.end()). The current + // implementation falls back to getOrCreate to avoid crashing on + // yet-unhandled pointer expressions, creating an empty origin for them. + if (It == DeclToOriginID.end()) + return getOrCreate(D); + + return It->second; + } + + OriginID getOrCreate(const Expr &E) { + auto It = ExprToOriginID.find(&E); + if (It != ExprToOriginID.end()) + return It->second; + + OriginID NewID = getNextOriginID(); + addOrigin(NewID, E); + ExprToOriginID[&E] = NewID; + return NewID; + } + + const Origin &getOrigin(OriginID ID) const { + assert(ID.Value < AllOrigins.size()); + return AllOrigins[ID.Value]; + } + + llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; } + + OriginID getOrCreate(const ValueDecl &D) { + auto It = DeclToOriginID.find(&D); + if (It != DeclToOriginID.end()) + return It->second; + OriginID NewID = getNextOriginID(); + addOrigin(NewID, D); + DeclToOriginID[&D] = NewID; + return NewID; + } + + void dump(OriginID OID, llvm::raw_ostream &OS) const { + OS << OID << " ("; + Origin O = getOrigin(OID); + if (const ValueDecl *VD = O.getDecl()) + OS << "Decl: " << VD->getNameAsString(); + else if (const Expr *E = O.getExpr()) + OS << "Expr: " << E->getStmtClassName(); + else + OS << "Unknown"; + OS << ")"; + } + +private: + OriginID getNextOriginID() { return NextOriginID++; } + + OriginID NextOriginID{0}; + /// TODO(opt): Profile and evaluate the usefullness of small buffer + /// optimisation. + llvm::SmallVector<Origin> AllOrigins; + llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID; + llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID; +}; + +/// An abstract base class for a single, atomic lifetime-relevant event. +class Fact { + +public: + enum class Kind : uint8_t { + /// A new loan is issued from a borrow expression (e.g., &x). + Issue, + /// A loan expires as its underlying storage is freed (e.g., variable goes + /// out of scope). + Expire, + /// An origin is propagated from a source to a destination (e.g., p = q). + /// This can also optionally kill the destination origin before flowing into + /// it. Otherwise, the source's loan set is merged into the destination's + /// loan set. + OriginFlow, + /// An origin escapes the function by flowing into the return value. + ReturnOfOrigin, + /// An origin is used (eg. appears as l-value expression like DeclRefExpr). + Use, + /// A marker for a specific point in the code, for testing. + TestPoint, + }; + +private: + Kind K; + +protected: + Fact(Kind K) : K(K) {} + +public: + virtual ~Fact() = default; + Kind getKind() const { return K; } + + template <typename T> const T *getAs() const { + if (T::classof(this)) + return static_cast<const T *>(this); + return nullptr; + } + + virtual void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &) const { + OS << "Fact (Kind: " << static_cast<int>(K) << ")\n"; + } +}; + +class IssueFact : public Fact { + LoanID LID; + OriginID OID; + +public: + static bool classof(const Fact *F) { return F->getKind() == Kind::Issue; } + + IssueFact(LoanID LID, OriginID OID) : Fact(Kind::Issue), LID(LID), OID(OID) {} + LoanID getLoanID() const { return LID; } + OriginID getOriginID() const { return OID; } + void dump(llvm::raw_ostream &OS, const LoanManager &LM, + const OriginManager &OM) const override { + OS << "Issue ("; + LM.getLoan(getLoanID()).dump(OS); + OS << ", ToOrigin: "; + OM.dump(getOriginID(), OS); + OS << ")\n"; + } +}; + +class ExpireFact : public Fact { + LoanID LID; + SourceLocation ExpiryLoc; + +public: + static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; } + + ExpireFact(LoanID LID, SourceLocation ExpiryLoc) + : Fact(Kind::Expire), LID(LID), ExpiryLoc(ExpiryLoc) {} + + LoanID getLoanID() const { return LID; } + SourceLocation getExpiryLoc() const { return ExpiryLoc; } + + void dump(llvm::raw_ostream &OS, const LoanManager &LM, + const OriginManager &) const override { + OS << "Expire ("; + LM.getLoan(getLoanID()).dump(OS); + OS << ")\n"; + } +}; + +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. + bool KillDest; + +public: + static bool classof(const Fact *F) { + return F->getKind() == Kind::OriginFlow; + } + + OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest) + : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc), + KillDest(KillDest) {} + + OriginID getDestOriginID() const { return OIDDest; } + OriginID getSrcOriginID() const { return OIDSrc; } + bool getKillDest() const { return KillDest; } + + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override { + OS << "OriginFlow (Dest: "; + OM.dump(getDestOriginID(), OS); + OS << ", Src: "; + OM.dump(getSrcOriginID(), OS); + OS << (getKillDest() ? "" : ", Merge"); + OS << ")\n"; + } +}; + +class ReturnOfOriginFact : public Fact { + OriginID OID; + +public: + static bool classof(const Fact *F) { + return F->getKind() == Kind::ReturnOfOrigin; + } + + ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {} + OriginID getReturnedOriginID() const { return OID; } + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override { + OS << "ReturnOfOrigin ("; + OM.dump(getReturnedOriginID(), OS); + OS << ")\n"; + } +}; + +class UseFact : public Fact { + const Expr *UseExpr; + // True if this use is a write operation (e.g., left-hand side of assignment). + // Write operations are exempted from use-after-free checks. + bool IsWritten = false; + +public: + static bool classof(const Fact *F) { return F->getKind() == Kind::Use; } + + UseFact(const Expr *UseExpr) : Fact(Kind::Use), UseExpr(UseExpr) {} + + OriginID getUsedOrigin(const OriginManager &OM) const { + // TODO: Remove const cast and make OriginManager::get as const. + return const_cast<OriginManager &>(OM).get(*UseExpr); + } + const Expr *getUseExpr() const { return UseExpr; } + void markAsWritten() { IsWritten = true; } + bool isWritten() const { return IsWritten; } + + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override { + OS << "Use ("; + OM.dump(getUsedOrigin(OM), OS); + OS << ", " << (isWritten() ? "Write" : "Read") << ")\n"; + } +}; + +/// A dummy-fact used to mark a specific point in the code for testing. +/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast. +class TestPointFact : public Fact { + StringRef Annotation; + +public: + static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; } + + explicit TestPointFact(StringRef Annotation) + : Fact(Kind::TestPoint), Annotation(Annotation) {} + + StringRef getAnnotation() const { return Annotation; } + + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &) const override { + OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n"; + } +}; + +class FactManager { +public: + llvm::ArrayRef<const Fact *> getFacts(const CFGBlock *B) const { + auto It = BlockToFactsMap.find(B); + if (It != BlockToFactsMap.end()) + return It->second; + return {}; + } + + void addBlockFacts(const CFGBlock *B, llvm::ArrayRef<Fact *> NewFacts) { + if (!NewFacts.empty()) + BlockToFactsMap[B].assign(NewFacts.begin(), NewFacts.end()); + } + + template <typename FactType, typename... Args> + FactType *createFact(Args &&...args) { + void *Mem = FactAllocator.Allocate<FactType>(); + return new (Mem) FactType(std::forward<Args>(args)...); + } + + void dump(const CFG &Cfg, AnalysisDeclContext &AC) const { + llvm::dbgs() << "==========================================\n"; + llvm::dbgs() << " Lifetime Analysis Facts:\n"; + llvm::dbgs() << "==========================================\n"; + if (const Decl *D = AC.getDecl()) + if (const auto *ND = dyn_cast<NamedDecl>(D)) + llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n"; + // Print blocks in the order as they appear in code for a stable ordering. + for (const CFGBlock *B : *AC.getAnalysis<PostOrderCFGView>()) { + llvm::dbgs() << " Block B" << B->getBlockID() << ":\n"; + auto It = BlockToFactsMap.find(B); + if (It != BlockToFactsMap.end()) { + for (const Fact *F : It->second) { + llvm::dbgs() << " "; + F->dump(llvm::dbgs(), LoanMgr, OriginMgr); + } + } + llvm::dbgs() << " End of Block\n"; + } + } + + LoanManager &getLoanMgr() { return LoanMgr; } + OriginManager &getOriginMgr() { return OriginMgr; } + +private: + LoanManager LoanMgr; + OriginManager OriginMgr; + llvm::DenseMap<const clang::CFGBlock *, llvm::SmallVector<const Fact *>> + BlockToFactsMap; + llvm::BumpPtrAllocator FactAllocator; +}; + +class FactGenerator : public ConstStmtVisitor<FactGenerator> { + using Base = ConstStmtVisitor<FactGenerator>; + +public: + FactGenerator(FactManager &FactMgr, AnalysisDeclContext &AC) + : FactMgr(FactMgr), AC(AC) {} + + void run() { + llvm::TimeTraceScope TimeProfile("FactGenerator"); + // Iterate through the CFG blocks in reverse post-order to ensure that + // initializations and destructions are processed in the correct sequence. + for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) { + CurrentBlockFacts.clear(); + for (unsigned I = 0; I < Block->size(); ++I) { + const CFGElement &Element = Block->Elements[I]; + if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) + Visit(CS->getStmt()); + else if (std::optional<CFGAutomaticObjDtor> DtorOpt = + Element.getAs<CFGAutomaticObjDtor>()) + handleDestructor(*DtorOpt); + } + FactMgr.addBlockFacts(Block, CurrentBlockFacts); + } + } + + void VisitDeclStmt(const DeclStmt *DS) { + for (const Decl *D : DS->decls()) + if (const auto *VD = dyn_cast<VarDecl>(D)) + if (hasOrigin(VD)) + if (const Expr *InitExpr = VD->getInit()) + killAndFlowOrigin(*VD, *InitExpr); + } + + void VisitDeclRefExpr(const DeclRefExpr *DRE) { + handleUse(DRE); + // For non-pointer/non-view types, a reference to the variable's storage + // is a borrow. We create a loan for it. + // For pointer/view types, we stick to the existing model for now and do + // not create an extra origin for the l-value expression itself. + + // TODO: A single origin for a `DeclRefExpr` for a pointer or view type is + // not sufficient to model the different levels of indirection. The current + // single-origin model cannot distinguish between a loan to the variable's + // storage and a loan to what it points to. A multi-origin model would be + // required for this. + if (!isPointerType(DRE->getType())) { + if (const Loan *L = createLoan(DRE)) { + OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE); + CurrentBlockFacts.push_back( + FactMgr.createFact<IssueFact>(L->ID, ExprOID)); + } + } + } + + void VisitCXXConstructExpr(const CXXConstructExpr *CCE) { + if (isGslPointerType(CCE->getType())) { + handleGSLPointerConstruction(CCE); + return; + } + } + + void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { + // Specifically for conversion operators, + // like `std::string_view p = std::string{};` + if (isGslPointerType(MCE->getType()) && + isa<CXXConversionDecl>(MCE->getCalleeDecl())) { + // The argument is the implicit object itself. + handleFunctionCall(MCE, MCE->getMethodDecl(), + {MCE->getImplicitObjectArgument()}, + /*IsGslConstruction=*/true); + } + if (const CXXMethodDecl *Method = MCE->getMethodDecl()) { + // Construct the argument list, with the implicit 'this' object as the + // first argument. + llvm::SmallVector<const Expr *, 4> Args; + Args.push_back(MCE->getImplicitObjectArgument()); + Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs()); + + handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false); + } + } + + void VisitCallExpr(const CallExpr *CE) { + handleFunctionCall(CE, CE->getDirectCallee(), + {CE->getArgs(), CE->getNumArgs()}); + } + + void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) { + /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized + /// pointers can use the same type of loan. + FactMgr.getOriginMgr().getOrCreate(*N); + } + + void VisitImplicitCastExpr(const ImplicitCastExpr *ICE) { + if (!hasOrigin(ICE)) + return; + // An ImplicitCastExpr node itself gets an origin, which flows from the + // origin of its sub-expression (after stripping its own parens/casts). + killAndFlowOrigin(*ICE, *ICE->getSubExpr()); + } + + void VisitUnaryOperator(const UnaryOperator *UO) { + if (UO->getOpcode() == UO_AddrOf) { + const Expr *SubExpr = UO->getSubExpr(); + // Taking address of a pointer-type expression is not yet supported and + // will be supported in multi-origin model. + if (isPointerType(SubExpr->getType())) + return; + // The origin of an address-of expression (e.g., &x) is the origin of + // its sub-expression (x). This fact will cause the dataflow analysis + // to propagate any loans held by the sub-expression's origin to the + // origin of this UnaryOperator expression. + killAndFlowOrigin(*UO, *SubExpr); + } + } + + void VisitReturnStmt(const ReturnStmt *RS) { + if (const Expr *RetExpr = RS->getRetValue()) { + if (hasOrigin(RetExpr)) { + OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr); + CurrentBlockFacts.push_back( + FactMgr.createFact<ReturnOfOriginFact>(OID)); + } + } + } + + void VisitBinaryOperator(const BinaryOperator *BO) { + if (BO->isAssignmentOp()) + handleAssignment(BO->getLHS(), BO->getRHS()); + } + + void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { + // Assignment operators have special "kill-then-propagate" semantics + // and are handled separately. + if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) { + handleAssignment(OCE->getArg(0), OCE->getArg(1)); + return; + } + handleFunctionCall(OCE, OCE->getDirectCallee(), + {OCE->getArgs(), OCE->getNumArgs()}, + /*IsGslConstruction=*/false); + } + + void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) { + // Check if this is a test point marker. If so, we are done with this + // expression. + if (handleTestPoint(FCE)) + return; + if (isGslPointerType(FCE->getType())) + killAndFlowOrigin(*FCE, *FCE->getSubExpr()); + } + + void VisitInitListExpr(const InitListExpr *ILE) { + if (!hasOrigin(ILE)) + return; + // For list initialization with a single element, like `View{...}`, the + // origin of the list itself is the origin of its single element. + if (ILE->getNumInits() == 1) + killAndFlowOrigin(*ILE, *ILE->getInit(0)); + } + + void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE) { + if (!hasOrigin(MTE)) + return; + // A temporary object's origin is the same as the origin of the + // expression that initializes it. + killAndFlowOrigin(*MTE, *MTE->getSubExpr()); + } + + void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) { + /// TODO: Also handle trivial destructors (e.g., for `int` + /// variables) which will never have a CFGAutomaticObjDtor node. + /// TODO: Handle loans to temporaries. + /// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the + /// lifetime ends. + const VarDecl *DestructedVD = DtorOpt.getVarDecl(); + if (!DestructedVD) + return; + // Iterate through all loans to see if any expire. + /// TODO(opt): Do better than a linear search to find loans associated with + /// 'DestructedVD'. + for (const Loan &L : FactMgr.getLoanMgr().getLoans()) { + const AccessPath &LoanPath = L.Path; + // Check if the loan is for a stack variable and if that variable + // is the one being destructed. + if (LoanPath.D == DestructedVD) + CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>( + L.ID, DtorOpt.getTriggerStmt()->getEndLoc())); + } + } + +private: + static bool isGslPointerType(QualType QT) { + if (const auto *RD = QT->getAsCXXRecordDecl()) { + // We need to check the template definition for specializations. + if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) + return CTSD->getSpecializedTemplate() + ->getTemplatedDecl() + ->hasAttr<PointerAttr>(); + return RD->hasAttr<PointerAttr>(); + } + return false; + } + + static bool isPointerType(QualType QT) { + return QT->isPointerOrReferenceType() || isGslPointerType(QT); + } + // Check if a type has an origin. + static bool hasOrigin(const Expr *E) { + return E->isGLValue() || isPointerType(E->getType()); + } + + static bool hasOrigin(const VarDecl *VD) { + return isPointerType(VD->getType()); + } + + void handleGSLPointerConstruction(const CXXConstructExpr *CCE) { + assert(isGslPointerType(CCE->getType())); + if (CCE->getNumArgs() != 1) + return; + if (hasOrigin(CCE->getArg(0))) + killAndFlowOrigin(*CCE, *CCE->getArg(0)); + else + // This could be a new borrow. + handleFunctionCall(CCE, CCE->getConstructor(), + {CCE->getArgs(), CCE->getNumArgs()}, + /*IsGslConstruction=*/true); + } + + /// Checks if a call-like expression creates a borrow by passing a value to a + /// reference parameter, creating an IssueFact if it does. + /// \param IsGslConstruction True if this is a GSL construction where all + /// argument origins should flow to the returned origin. + void handleFunctionCall(const Expr *Call, const FunctionDecl *FD, + ArrayRef<const Expr *> Args, + bool IsGslConstruction = false) { + // Ignore functions returning values with no origin. + if (!FD || !hasOrigin(Call)) + return; + auto IsArgLifetimeBound = [FD](unsigned I) -> bool { + const ParmVarDecl *PVD = nullptr; + if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); + Method && Method->isInstance()) { + if (I == 0) + // For the 'this' argument, the attribute is on the method itself. + return implicitObjectParamIsLifetimeBound(Method); + if ((I - 1) < Method->getNumParams()) + // For explicit arguments, find the corresponding parameter + // declaration. + PVD = Method->getParamDecl(I - 1); + } else if (I < FD->getNumParams()) + // For free functions or static methods. + PVD = FD->getParamDecl(I); + return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false; + }; + if (Args.empty()) + return; + bool killedSrc = false; + for (unsigned I = 0; I < Args.size(); ++I) + if (IsGslConstruction || IsArgLifetimeBound(I)) { + if (!killedSrc) { + killedSrc = true; + killAndFlowOrigin(*Call, *Args[I]); + } else + flowOrigin(*Call, *Args[I]); + } + } + + /// Creates a loan for the storage path of a given declaration reference. + /// This function should be called whenever a DeclRefExpr represents a borrow. + /// \param DRE The declaration reference expression that initiates the borrow. + /// \return The new Loan on success, nullptr otherwise. + const Loan *createLoan(const DeclRefExpr *DRE) { + if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) { + AccessPath Path(VD); + // The loan is created at the location of the DeclRefExpr. + return &FactMgr.getLoanMgr().addLoan(Path, DRE); + } + return nullptr; + } + + template <typename Destination, typename Source> + void flowOrigin(const Destination &D, const Source &S) { + OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D); + OriginID SrcOID = FactMgr.getOriginMgr().get(S); + CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( + DestOID, SrcOID, /*KillDest=*/false)); + } + + template <typename Destination, typename Source> + void killAndFlowOrigin(const Destination &D, const Source &S) { + OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D); + OriginID SrcOID = FactMgr.getOriginMgr().get(S); + CurrentBlockFacts.push_back( + FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID, /*KillDest=*/true)); + } + + /// Checks if the expression is a `void("__lifetime_test_point_...")` cast. + /// If so, creates a `TestPointFact` and returns true. + bool handleTestPoint(const CXXFunctionalCastExpr *FCE) { + if (!FCE->getType()->isVoidType()) + return false; + + const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts(); + if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) { + llvm::StringRef LiteralValue = SL->getString(); + const std::string Prefix = "__lifetime_test_point_"; + + if (LiteralValue.starts_with(Prefix)) { + StringRef Annotation = LiteralValue.drop_front(Prefix.length()); + CurrentBlockFacts.push_back( + FactMgr.createFact<TestPointFact>(Annotation)); + return true; + } + } + return false; + } + + void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr) { + if (!hasOrigin(LHSExpr)) + return; + // Find the underlying variable declaration for the left-hand side. + if (const auto *DRE_LHS = + dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) { + markUseAsWrite(DRE_LHS); + if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) { + // Kill the old loans of the destination origin and flow the new loans + // from the source origin. + killAndFlowOrigin(*VD_LHS, *RHSExpr); + } + } + } + + // 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 handleUse(const DeclRefExpr *DRE) { + if (isPointerType(DRE->getType())) { + UseFact *UF = FactMgr.createFact<UseFact>(DRE); + CurrentBlockFacts.push_back(UF); + assert(!UseFacts.contains(DRE)); + UseFacts[DRE] = UF; + } + } + + void markUseAsWrite(const DeclRefExpr *DRE) { + if (!isPointerType(DRE->getType())) + return; + assert(UseFacts.contains(DRE)); + UseFacts[DRE]->markAsWritten(); + } + + FactManager &FactMgr; + AnalysisDeclContext &AC; + llvm::SmallVector<Fact *> CurrentBlockFacts; + // To distinguish between reads and writes for use-after-free checks, this map + // stores the `UseFact` for each `DeclRefExpr`. We initially identify all + // `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; +}; + +// ========================================================================= // +// Generic Dataflow Analysis +// ========================================================================= // + +enum class Direction { Forward, Backward }; + +/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific +/// `Fact`. identified by a lifetime-related event (`Fact`). +/// +/// A `ProgramPoint` has "after" semantics: it represents the location +/// immediately after its corresponding `Fact`. +using ProgramPoint = const Fact *; + +/// A generic, policy-based driver for dataflow analyses. It combines +/// the dataflow runner and the transferer logic into a single class hierarchy. +/// +/// The derived class is expected to provide: +/// - A `Lattice` type. +/// - `StringRef getAnalysisName() const` +/// - `Lattice getInitialState();` The initial state of the analysis. +/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths. +/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single +/// lifetime-relevant `Fact` transforms the lattice state. Only overloads +/// for facts relevant to the analysis need to be implemented. +/// +/// \tparam Derived The CRTP derived class that implements the specific +/// analysis. +/// \tparam LatticeType The dataflow lattice used by the analysis. +/// \tparam Dir The direction of the analysis (Forward or Backward). +/// TODO: Maybe use the dataflow framework! The framework might need changes +/// to support the current comparison done at block-entry. +template <typename Derived, typename LatticeType, Direction Dir> +class DataflowAnalysis { +public: + using Lattice = LatticeType; + using Base = DataflowAnalysis<Derived, Lattice, Dir>; + +private: + const CFG &Cfg; + AnalysisDeclContext &AC; + + /// The dataflow state before a basic block is processed. + llvm::DenseMap<const CFGBlock *, Lattice> InStates; + /// The dataflow state after a basic block is processed. + llvm::DenseMap<const CFGBlock *, Lattice> OutStates; + /// The dataflow state at a Program Point. + /// In a forward analysis, this is the state after the Fact at that point has + /// been applied, while in a backward analysis, it is the state before. + llvm::DenseMap<ProgramPoint, Lattice> PerPointStates; + + static constexpr bool isForward() { return Dir == Direction::Forward; } + +protected: + FactManager &AllFacts; + + explicit DataflowAnalysis(const CFG &C, AnalysisDeclContext &AC, + FactManager &F) + : Cfg(C), AC(AC), AllFacts(F) {} + +public: + void run() { + Derived &D = static_cast<Derived &>(*this); + llvm::TimeTraceScope Time(D.getAnalysisName()); + + using Worklist = + std::conditional_t<Dir == Direction::Forward, ForwardDataflowWorklist, + BackwardDataflowWorklist>; + Worklist W(Cfg, AC); + + const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit(); + InStates[Start] = D.getInitialState(); + W.enqueueBlock(Start); + + while (const CFGBlock *B = W.dequeue()) { + Lattice StateIn = *getInState(B); + Lattice StateOut = transferBlock(B, StateIn); + OutStates[B] = StateOut; + for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) { + if (!AdjacentB) + continue; + std::optional<Lattice> OldInState = getInState(AdjacentB); + Lattice NewInState = + !OldInState ? StateOut : D.join(*OldInState, StateOut); + // Enqueue the adjacent block if its in-state has changed or if we have + // never seen it. + if (!OldInState || NewInState != *OldInState) { + InStates[AdjacentB] = NewInState; + W.enqueueBlock(AdjacentB); + } + } + } + } + +protected: + Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); } + + std::optional<Lattice> getInState(const CFGBlock *B) const { + auto It = InStates.find(B); + if (It == InStates.end()) + return std::nullopt; + return It->second; + } + + Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); } + + void dump() const { + const Derived *D = static_cast<const Derived *>(this); + llvm::dbgs() << "==========================================\n"; + llvm::dbgs() << D->getAnalysisName() << " results:\n"; + llvm::dbgs() << "==========================================\n"; + const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry(); + getOutState(&B).dump(llvm::dbgs()); + } + +private: + /// Computes the state at one end of a block by applying all its facts + /// sequentially to a given state from the other end. + Lattice transferBlock(const CFGBlock *Block, Lattice State) { + auto Facts = AllFacts.getFacts(Block); + if constexpr (isForward()) { + for (const Fact *F : Facts) { + State = transferFact(State, F); + PerPointStates[F] = State; + } + } else { + for (const Fact *F : llvm::reverse(Facts)) { + // In backward analysis, capture the state before applying the fact. + PerPointStates[F] = State; + State = transferFact(State, F); + } + } + return State; + } + + Lattice transferFact(Lattice In, const Fact *F) { + assert(F); + Derived *D = static_cast<Derived *>(this); + switch (F->getKind()) { + case Fact::Kind::Issue: + return D->transfer(In, *F->getAs<IssueFact>()); + case Fact::Kind::Expire: + return D->transfer(In, *F->getAs<ExpireFact>()); + case Fact::Kind::OriginFlow: + return D->transfer(In, *F->getAs<OriginFlowFact>()); + case Fact::Kind::ReturnOfOrigin: + return D->transfer(In, *F->getAs<ReturnOfOriginFact>()); + case Fact::Kind::Use: + return D->transfer(In, *F->getAs<UseFact>()); + case Fact::Kind::TestPoint: + return D->transfer(In, *F->getAs<TestPointFact>()); + } + llvm_unreachable("Unknown fact kind"); + } + +public: + Lattice transfer(Lattice In, const IssueFact &) { return In; } + Lattice transfer(Lattice In, const ExpireFact &) { return In; } + Lattice transfer(Lattice In, const OriginFlowFact &) { return In; } + Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; } + Lattice transfer(Lattice In, const UseFact &) { return In; } + Lattice transfer(Lattice In, const TestPointFact &) { return In; } +}; + +namespace utils { + +/// Computes the union of two ImmutableSets. +template <typename T> +static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, + llvm::ImmutableSet<T> B, + typename llvm::ImmutableSet<T>::Factory &F) { + if (A.getHeight() < B.getHeight()) + std::swap(A, B); + for (const T &E : B) + A = F.add(A, E); + return A; +} + +/// Describes the strategy for joining two `ImmutableMap` instances, primarily +/// differing in how they handle keys that are unique to one of the maps. +/// +/// A `Symmetric` join is universally correct, while an `Asymmetric` join +/// serves as a performance optimization. The latter is applicable only when the +/// join operation possesses a left identity element, allowing for a more +/// efficient, one-sided merge. +enum class JoinKind { + /// A symmetric join applies the `JoinValues` operation to keys unique to + /// either map, ensuring that values from both maps contribute to the result. + Symmetric, + /// An asymmetric join preserves keys unique to the first map as-is, while + /// applying the `JoinValues` operation only to keys unique to the second map. + Asymmetric, +}; + +/// Computes the key-wise union of two ImmutableMaps. +// TODO(opt): This key-wise join is a performance bottleneck. A more +// efficient merge could be implemented using a Patricia Trie or HAMT +// instead of the current AVL-tree-based ImmutableMap. +template <typename K, typename V, typename Joiner> +static llvm::ImmutableMap<K, V> +join(const llvm::ImmutableMap<K, V> &A, const llvm::ImmutableMap<K, V> &B, + typename llvm::ImmutableMap<K, V>::Factory &F, Joiner JoinValues, + JoinKind Kind) { + if (A.getHeight() < B.getHeight()) + return join(B, A, F, JoinValues, Kind); + + // For each element in B, join it with the corresponding element in A + // (or with an empty value if it doesn't exist in A). + llvm::ImmutableMap<K, V> Res = A; + for (const auto &Entry : B) { + const K &Key = Entry.first; + const V &ValB = Entry.second; + Res = F.add(Res, Key, JoinValues(A.lookup(Key), &ValB)); + } + if (Kind == JoinKind::Symmetric) { + for (const auto &Entry : A) { + const K &Key = Entry.first; + const V &ValA = Entry.second; + if (!B.contains(Key)) + Res = F.add(Res, Key, JoinValues(&ValA, nullptr)); + } + } + return Res; +} +} // namespace utils + +// ========================================================================= // +// Loan Propagation Analysis +// ========================================================================= // + +/// Represents the dataflow lattice for loan propagation. +/// +/// This lattice tracks which loans each origin may hold at a given program +/// point.The lattice has a finite height: An origin's loan set is bounded by +/// the total number of loans in the function. +/// TODO(opt): To reduce the lattice size, propagate origins of declarations, +/// not expressions, because expressions are not visible across blocks. +struct LoanPropagationLattice { + /// The map from an origin to the set of loans it contains. + OriginLoanMap Origins = OriginLoanMap(nullptr); + + explicit LoanPropagationLattice(const OriginLoanMap &S) : Origins(S) {} + LoanPropagationLattice() = default; + + bool operator==(const LoanPropagationLattice &Other) const { + return Origins == Other.Origins; + } + bool operator!=(const LoanPropagationLattice &Other) const { + return !(*this == Other); + } + + void dump(llvm::raw_ostream &OS) const { + OS << "LoanPropagationLattice State:\n"; + if (Origins.isEmpty()) + OS << " <empty>\n"; + for (const auto &Entry : Origins) { + if (Entry.second.isEmpty()) + OS << " Origin " << Entry.first << " contains no loans\n"; + for (const LoanID &LID : Entry.second) + OS << " Origin " << Entry.first << " contains Loan " << LID << "\n"; + } + } +}; + +/// The analysis that tracks which loans belong to which origins. +class LoanPropagationAnalysis + : public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice, + Direction::Forward> { + OriginLoanMap::Factory &OriginLoanMapFactory; + LoanSet::Factory &LoanSetFactory; + +public: + LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, + OriginLoanMap::Factory &OriginLoanMapFactory, + LoanSet::Factory &LoanSetFactory) + : DataflowAnalysis(C, AC, F), OriginLoanMapFactory(OriginLoanMapFactory), + LoanSetFactory(LoanSetFactory) {} + + using Base::transfer; + + StringRef getAnalysisName() const { return "LoanPropagation"; } + + Lattice getInitialState() { return Lattice{}; } + + /// Merges two lattices by taking the union of loans for each origin. + // TODO(opt): Keep the state small by removing origins which become dead. + Lattice join(Lattice A, Lattice B) { + OriginLoanMap JoinedOrigins = utils::join( + A.Origins, B.Origins, OriginLoanMapFactory, + [&](const LoanSet *S1, const LoanSet *S2) { + assert((S1 || S2) && "unexpectedly merging 2 empty sets"); + if (!S1) + return *S2; + if (!S2) + return *S1; + return utils::join(*S1, *S2, LoanSetFactory); + }, + // Asymmetric join is a performance win. For origins present only on one + // branch, the loan set can be carried over as-is. + utils::JoinKind::Asymmetric); + return Lattice(JoinedOrigins); + } + + /// A new loan is issued to the origin. Old loans are erased. + Lattice transfer(Lattice In, const IssueFact &F) { + OriginID OID = F.getOriginID(); + LoanID LID = F.getLoanID(); + return LoanPropagationLattice(OriginLoanMapFactory.add( + In.Origins, OID, + LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID))); + } + + /// A flow from source to destination. If `KillDest` is true, this replaces + /// the destination's loans with the source's. Otherwise, the source's loans + /// are merged into the destination's. + Lattice transfer(Lattice In, const OriginFlowFact &F) { + OriginID DestOID = F.getDestOriginID(); + OriginID SrcOID = F.getSrcOriginID(); + + LoanSet DestLoans = + F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID); + LoanSet SrcLoans = getLoans(In, SrcOID); + LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory); + + return LoanPropagationLattice( + OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans)); + } + + LoanSet getLoans(OriginID OID, ProgramPoint P) const { + return getLoans(getState(P), OID); + } + +private: + LoanSet getLoans(Lattice L, OriginID OID) const { + if (auto *Loans = L.Origins.lookup(OID)) + return *Loans; + return LoanSetFactory.getEmptySet(); + } +}; + +// ========================================================================= // +// Live Origins Analysis +// ========================================================================= // +// +// A backward dataflow analysis that determines which origins are "live" at each +// program point. An origin is "live" at a program point if there's a potential +// future use of the pointer it represents. Liveness is "generated" by a read of +// origin's loan set (e.g., a `UseFact`) and is "killed" (i.e., it stops being +// live) when its loan set is overwritten (e.g. a OriginFlow killing the +// destination origin). +// +// This information is used for detecting use-after-free errors, as it allows us +// to check if a live origin holds a loan to an object that has already expired. +// ========================================================================= // + +/// Information about why an origin is live at a program point. +struct LivenessInfo { + /// The use that makes the origin live. If liveness is propagated from + /// multiple uses along different paths, this will point to the use appearing + /// earlier in the translation unit. + /// This is 'null' when the origin is not live. + const UseFact *CausingUseFact; + /// The kind of liveness of the origin. + /// `Must`: The origin is live on all control-flow paths from the current + /// point to the function's exit (i.e. the current point is dominated by a set + /// of uses). + /// `Maybe`: indicates it is live on some but not all paths. + /// + /// This determines the diagnostic's confidence level. + /// `Must`-be-alive at expiration implies a definite use-after-free, + /// while `Maybe`-be-alive suggests a potential one on some paths. + LivenessKind Kind; + + LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {} + LivenessInfo(const UseFact *UF, LivenessKind K) + : CausingUseFact(UF), Kind(K) {} + + bool operator==(const LivenessInfo &Other) const { + return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind; + } + bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); } + + void Profile(llvm::FoldingSetNodeID &IDBuilder) const { + IDBuilder.AddPointer(CausingUseFact); + IDBuilder.Add(Kind); + } +}; + +using LivenessMap = llvm::ImmutableMap<OriginID, LivenessInfo>; + +/// The dataflow lattice for origin liveness analysis. +/// It tracks which origins are live, why they're live (which UseFact), +/// and the confidence level of that liveness. +struct LivenessLattice { + LivenessMap LiveOrigins; + + LivenessLattice() : LiveOrigins(nullptr) {}; + + explicit LivenessLattice(LivenessMap L) : LiveOrigins(L) {} + + bool operator==(const LivenessLattice &Other) const { + return LiveOrigins == Other.LiveOrigins; + } + + bool operator!=(const LivenessLattice &Other) const { + return !(*this == Other); + } + + void dump(llvm::raw_ostream &OS, const OriginManager &OM) const { + if (LiveOrigins.isEmpty()) + OS << " <empty>\n"; + for (const auto &Entry : LiveOrigins) { + OriginID OID = Entry.first; + const LivenessInfo &Info = Entry.second; + OS << " "; + OM.dump(OID, OS); + OS << " is "; + switch (Info.Kind) { + case LivenessKind::Must: + OS << "definitely"; + break; + case LivenessKind::Maybe: + OS << "maybe"; + break; + case LivenessKind::Dead: + llvm_unreachable("liveness kind of live origins should not be dead."); + } + OS << " live at this point\n"; + } + } +}; + +/// The analysis that tracks which origins are live, with granular information +/// about the causing use fact and confidence level. This is a backward +/// analysis. +class LiveOriginAnalysis + : public DataflowAnalysis<LiveOriginAnalysis, LivenessLattice, + Direction::Backward> { + FactManager &FactMgr; + LivenessMap::Factory &Factory; + +public: + LiveOriginAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, + LivenessMap::Factory &SF) + : DataflowAnalysis(C, AC, F), FactMgr(F), Factory(SF) {} + using DataflowAnalysis<LiveOriginAnalysis, Lattice, + Direction::Backward>::transfer; + + StringRef getAnalysisName() const { return "LiveOrigins"; } + + Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); } + + /// Merges two lattices by combining liveness information. + /// When the same origin has different confidence levels, we take the lower + /// one. + Lattice join(Lattice L1, Lattice L2) const { + LivenessMap Merged = L1.LiveOrigins; + // Take the earliest UseFact to make the join hermetic and commutative. + auto CombineUseFact = [](const UseFact &A, + const UseFact &B) -> const UseFact * { + return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A + : &B; + }; + auto CombineLivenessKind = [](LivenessKind K1, + LivenessKind K2) -> LivenessKind { + assert(K1 != LivenessKind::Dead && "LivenessKind should not be dead."); + assert(K2 != LivenessKind::Dead && "LivenessKind should not be dead."); + // Only return "Must" if both paths are "Must", otherwise Maybe. + if (K1 == LivenessKind::Must && K2 == LivenessKind::Must) + return LivenessKind::Must; + return LivenessKind::Maybe; + }; + auto CombineLivenessInfo = [&](const LivenessInfo *L1, + const LivenessInfo *L2) -> LivenessInfo { + assert((L1 || L2) && "unexpectedly merging 2 empty sets"); + if (!L1) + return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe); + if (!L2) + return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe); + return LivenessInfo( + CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact), + CombineLivenessKind(L1->Kind, L2->Kind)); + }; + return Lattice(utils::join( + L1.LiveOrigins, L2.LiveOrigins, Factory, CombineLivenessInfo, + // A symmetric join is required here. If an origin is live on one + // branch but not the other, its confidence must be demoted to `Maybe`. + utils::JoinKind::Symmetric)); + } + + /// A read operation makes the origin live with definite confidence, as it + /// dominates this program point. A write operation kills the liveness of + /// the origin since it overwrites the value. + Lattice transfer(Lattice In, const UseFact &UF) { + OriginID OID = UF.getUsedOrigin(FactMgr.getOriginMgr()); + // Write kills liveness. + if (UF.isWritten()) + return Lattice(Factory.remove(In.LiveOrigins, OID)); + // Read makes origin live with definite confidence (dominates this point). + return Lattice(Factory.add(In.LiveOrigins, OID, + LivenessInfo(&UF, LivenessKind::Must))); + } + + /// Issuing a new loan to an origin kills its liveness. + Lattice transfer(Lattice In, const IssueFact &IF) { + return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID())); + } + + /// An OriginFlow kills the liveness of the destination origin if `KillDest` + /// is true. Otherwise, it propagates liveness from destination to source. + Lattice transfer(Lattice In, const OriginFlowFact &OF) { + if (!OF.getKillDest()) + return In; + return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID())); + } + + LivenessMap getLiveOrigins(ProgramPoint P) const { + return getState(P).LiveOrigins; + } + + // Dump liveness values on all test points in the program. + void dump(llvm::raw_ostream &OS, const LifetimeSafetyAnalysis &LSA) const { + llvm::dbgs() << "==========================================\n"; + llvm::dbgs() << getAnalysisName() << " results:\n"; + llvm::dbgs() << "==========================================\n"; + for (const auto &Entry : LSA.getTestPoints()) { + OS << "TestPoint: " << Entry.getKey() << "\n"; + getState(Entry.getValue()).dump(OS, FactMgr.getOriginMgr()); + } + } +}; + +// ========================================================================= // +// Lifetime checker and Error reporter +// ========================================================================= // + +/// Struct to store the complete context for a potential lifetime violation. +struct PendingWarning { + SourceLocation ExpiryLoc; // Where the loan expired. + const Expr *UseExpr; // Where the origin holding this loan was used. + Confidence ConfidenceLevel; +}; + +class LifetimeChecker { +private: + llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; + LoanPropagationAnalysis &LoanPropagation; + LiveOriginAnalysis &LiveOrigins; + FactManager &FactMgr; + AnalysisDeclContext &ADC; + LifetimeSafetyReporter *Reporter; + +public: + LifetimeChecker(LoanPropagationAnalysis &LPA, LiveOriginAnalysis &LOA, + FactManager &FM, AnalysisDeclContext &ADC, + LifetimeSafetyReporter *Reporter) + : LoanPropagation(LPA), LiveOrigins(LOA), FactMgr(FM), ADC(ADC), + Reporter(Reporter) {} + + void run() { + llvm::TimeTraceScope TimeProfile("LifetimeChecker"); + for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) + for (const Fact *F : FactMgr.getFacts(B)) + if (const auto *EF = F->getAs<ExpireFact>()) + checkExpiry(EF); + issuePendingWarnings(); + } + + /// Checks for use-after-free errors when a loan expires. + /// + /// This method examines all live origins at the expiry point and determines + /// if any of them hold the expiring loan. If so, it creates a pending + /// warning with the appropriate confidence level based on the liveness + /// information. The confidence reflects whether the origin is definitely + /// or maybe live at this point. + /// + /// Note: This implementation considers only the confidence of origin + /// liveness. Future enhancements could also consider the confidence of loan + /// propagation (e.g., a loan may only be held on some execution paths). + void checkExpiry(const ExpireFact *EF) { + LoanID ExpiredLoan = EF->getLoanID(); + LivenessMap Origins = LiveOrigins.getLiveOrigins(EF); + Confidence CurConfidence = Confidence::None; + const UseFact *BadUse = nullptr; + for (auto &[OID, LiveInfo] : Origins) { + LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF); + if (!HeldLoans.contains(ExpiredLoan)) + continue; + // Loan is defaulted. + Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind); + if (CurConfidence < NewConfidence) { + CurConfidence = NewConfidence; + BadUse = LiveInfo.CausingUseFact; + } + } + if (!BadUse) + return; + // We have a use-after-free. + Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel; + if (LastConf >= CurConfidence) + return; + FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(), + /*UseExpr=*/BadUse->getUseExpr(), + /*ConfidenceLevel=*/CurConfidence}; + } + + static Confidence livenessKindToConfidence(LivenessKind K) { + switch (K) { + case LivenessKind::Must: + return Confidence::Definite; + case LivenessKind::Maybe: + return Confidence::Maybe; + case LivenessKind::Dead: + return Confidence::None; + } + llvm_unreachable("unknown liveness kind"); + } + + void issuePendingWarnings() { + if (!Reporter) + return; + for (const auto &[LID, Warning] : FinalWarningsMap) { + const Loan &L = FactMgr.getLoanMgr().getLoan(LID); + const Expr *IssueExpr = L.IssueExpr; + Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr, + Warning.ExpiryLoc, Warning.ConfidenceLevel); + } + } +}; + +// ========================================================================= // +// LifetimeSafetyAnalysis Class Implementation +// ========================================================================= // + +/// An object to hold the factories for immutable collections, ensuring +/// that all created states share the same underlying memory management. +struct LifetimeFactory { + llvm::BumpPtrAllocator Allocator; + OriginLoanMap::Factory OriginMapFactory{Allocator, /*canonicalize=*/false}; + LoanSet::Factory LoanSetFactory{Allocator, /*canonicalize=*/false}; + LivenessMap::Factory LivenessMapFactory{Allocator, /*canonicalize=*/false}; +}; + +// We need this here for unique_ptr with forward declared class. +LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default; + +LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter) + : AC(AC), Reporter(Reporter), Factory(std::make_unique<LifetimeFactory>()), + FactMgr(std::make_unique<FactManager>()) {} + +void LifetimeSafetyAnalysis::run() { + llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis"); + + const CFG &Cfg = *AC.getCFG(); + DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(), + /*ShowColors=*/true)); + + FactGenerator FactGen(*FactMgr, AC); + FactGen.run(); + DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC)); + + /// TODO(opt): Consider optimizing individual blocks before running the + /// dataflow analysis. + /// 1. Expression Origins: These are assigned once and read at most once, + /// forming simple chains. These chains can be compressed into a single + /// assignment. + /// 2. Block-Local Loans: Origins of expressions are never read by other + /// blocks; only Decls are visible. Therefore, loans in a block that + /// never reach an Origin associated with a Decl can be safely dropped by + /// the analysis. + /// 3. Collapse ExpireFacts belonging to same source location into a single + /// Fact. + LoanPropagation = std::make_unique<LoanPropagationAnalysis>( + Cfg, AC, *FactMgr, Factory->OriginMapFactory, Factory->LoanSetFactory); + LoanPropagation->run(); + + LiveOrigins = std::make_unique<LiveOriginAnalysis>( + Cfg, AC, *FactMgr, Factory->LivenessMapFactory); + LiveOrigins->run(); + DEBUG_WITH_TYPE("LiveOrigins", LiveOrigins->dump(llvm::dbgs(), *this)); + + LifetimeChecker Checker(*LoanPropagation, *LiveOrigins, *FactMgr, AC, + Reporter); + Checker.run(); +} + +LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID, + ProgramPoint PP) const { + assert(LoanPropagation && "Analysis has not been run."); + return LoanPropagation->getLoans(OID, PP); +} + +std::optional<OriginID> +LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const { + assert(FactMgr && "FactManager not initialized"); + // This assumes the OriginManager's `get` can find an existing origin. + // We might need a `find` method on OriginManager to avoid `getOrCreate` logic + // in a const-query context if that becomes an issue. + return FactMgr->getOriginMgr().get(*D); +} + +std::vector<LoanID> +LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const { + assert(FactMgr && "FactManager not initialized"); + std::vector<LoanID> Result; + for (const Loan &L : FactMgr->getLoanMgr().getLoans()) + if (L.Path.D == VD) + Result.push_back(L.ID); + return Result; +} + +std::vector<std::pair<OriginID, LivenessKind>> +LifetimeSafetyAnalysis::getLiveOriginsAtPoint(ProgramPoint PP) const { + assert(LiveOrigins && "LiveOriginAnalysis has not been run."); + std::vector<std::pair<OriginID, LivenessKind>> Result; + for (auto &[OID, Info] : LiveOrigins->getLiveOrigins(PP)) + Result.push_back({OID, Info.Kind}); + return Result; +} + +llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const { + assert(FactMgr && "FactManager not initialized"); + llvm::StringMap<ProgramPoint> AnnotationToPointMap; + for (const CFGBlock *Block : *AC.getCFG()) { + for (const Fact *F : FactMgr->getFacts(Block)) { + if (const auto *TPF = F->getAs<TestPointFact>()) { + StringRef PointName = TPF->getAnnotation(); + assert(AnnotationToPointMap.find(PointName) == + AnnotationToPointMap.end() && + "more than one test points with the same name"); + AnnotationToPointMap[PointName] = F; + } + } + } + return AnnotationToPointMap; +} +} // namespace internal + +void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter) { + internal::LifetimeSafetyAnalysis Analysis(AC, Reporter); + Analysis.run(); +} +} // namespace clang::lifetimes diff --git a/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt b/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt deleted file mode 100644 index 8584292..0000000 --- a/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_clang_library(clangAnalysisLifetimeSafety - Checker.cpp - Facts.cpp - FactsGenerator.cpp - LifetimeAnnotations.cpp - LifetimeSafety.cpp - LiveOrigins.cpp - Loans.cpp - LoanPropagation.cpp - Origins.cpp - ) - diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp deleted file mode 100644 index c443c3a..0000000 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ /dev/null @@ -1,130 +0,0 @@ -//===- Checker.cpp - C++ Lifetime Safety Checker ----------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file implements the LifetimeChecker, which detects use-after-free -// errors by checking if live origins hold loans that have expired. -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h" -#include "clang/AST/Expr.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h" -#include "clang/Analysis/Analyses/PostOrderCFGView.h" -#include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Basic/SourceLocation.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/Support/ErrorHandling.h" -#include "llvm/Support/TimeProfiler.h" - -namespace clang::lifetimes::internal { - -static Confidence livenessKindToConfidence(LivenessKind K) { - switch (K) { - case LivenessKind::Must: - return Confidence::Definite; - case LivenessKind::Maybe: - return Confidence::Maybe; - case LivenessKind::Dead: - return Confidence::None; - } - llvm_unreachable("unknown liveness kind"); -} - -namespace { - -/// Struct to store the complete context for a potential lifetime violation. -struct PendingWarning { - SourceLocation ExpiryLoc; // Where the loan expired. - const Expr *UseExpr; // Where the origin holding this loan was used. - Confidence ConfidenceLevel; -}; - -class LifetimeChecker { -private: - llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; - const LoanPropagationAnalysis &LoanPropagation; - const LiveOriginsAnalysis &LiveOrigins; - const FactManager &FactMgr; - LifetimeSafetyReporter *Reporter; - -public: - LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation, - const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM, - AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter) - : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM), - Reporter(Reporter) { - for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) - for (const Fact *F : FactMgr.getFacts(B)) - if (const auto *EF = F->getAs<ExpireFact>()) - checkExpiry(EF); - issuePendingWarnings(); - } - - /// Checks for use-after-free errors when a loan expires. - /// - /// This method examines all live origins at the expiry point and determines - /// if any of them hold the expiring loan. If so, it creates a pending - /// warning with the appropriate confidence level based on the liveness - /// information. The confidence reflects whether the origin is definitely - /// or maybe live at this point. - /// - /// Note: This implementation considers only the confidence of origin - /// liveness. Future enhancements could also consider the confidence of loan - /// propagation (e.g., a loan may only be held on some execution paths). - void checkExpiry(const ExpireFact *EF) { - LoanID ExpiredLoan = EF->getLoanID(); - LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF); - Confidence CurConfidence = Confidence::None; - const UseFact *BadUse = nullptr; - for (auto &[OID, LiveInfo] : Origins) { - LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF); - if (!HeldLoans.contains(ExpiredLoan)) - continue; - // Loan is defaulted. - Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind); - if (CurConfidence < NewConfidence) { - CurConfidence = NewConfidence; - BadUse = LiveInfo.CausingUseFact; - } - } - if (!BadUse) - return; - // We have a use-after-free. - Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel; - if (LastConf >= CurConfidence) - return; - FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(), - /*UseExpr=*/BadUse->getUseExpr(), - /*ConfidenceLevel=*/CurConfidence}; - } - - void issuePendingWarnings() { - if (!Reporter) - return; - for (const auto &[LID, Warning] : FinalWarningsMap) { - const Loan &L = FactMgr.getLoanMgr().getLoan(LID); - const Expr *IssueExpr = L.IssueExpr; - Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr, - Warning.ExpiryLoc, Warning.ConfidenceLevel); - } - } -}; -} // namespace - -void runLifetimeChecker(const LoanPropagationAnalysis &LP, - const LiveOriginsAnalysis &LO, - const FactManager &FactMgr, AnalysisDeclContext &ADC, - LifetimeSafetyReporter *Reporter) { - llvm::TimeTraceScope TimeProfile("LifetimeChecker"); - LifetimeChecker Checker(LP, LO, FactMgr, ADC, Reporter); -} - -} // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h deleted file mode 100644 index 2f7bcb6..0000000 --- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h +++ /dev/null @@ -1,188 +0,0 @@ -//===- Dataflow.h - Generic Dataflow Analysis Framework --------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines a generic, policy-based driver for dataflow analyses. -// It provides a flexible framework that combines the dataflow runner and -// transfer functions, allowing derived classes to implement specific analyses -// by defining their lattice, join, and transfer functions. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H -#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H - -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Analysis/CFG.h" -#include "clang/Analysis/FlowSensitive/DataflowWorklist.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/ErrorHandling.h" -#include "llvm/Support/TimeProfiler.h" -#include <optional> - -namespace clang::lifetimes::internal { - -enum class Direction { Forward, Backward }; - -/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific -/// `Fact`. identified by a lifetime-related event (`Fact`). -/// -/// A `ProgramPoint` has "after" semantics: it represents the location -/// immediately after its corresponding `Fact`. -using ProgramPoint = const Fact *; - -/// A generic, policy-based driver for dataflow analyses. It combines -/// the dataflow runner and the transferer logic into a single class hierarchy. -/// -/// The derived class is expected to provide: -/// - A `Lattice` type. -/// - `StringRef getAnalysisName() const` -/// - `Lattice getInitialState();` The initial state of the analysis. -/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths. -/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single -/// lifetime-relevant `Fact` transforms the lattice state. Only overloads -/// for facts relevant to the analysis need to be implemented. -/// -/// \tparam Derived The CRTP derived class that implements the specific -/// analysis. -/// \tparam LatticeType The dataflow lattice used by the analysis. -/// \tparam Dir The direction of the analysis (Forward or Backward). -/// TODO: Maybe use the dataflow framework! The framework might need changes -/// to support the current comparison done at block-entry. -template <typename Derived, typename LatticeType, Direction Dir> -class DataflowAnalysis { -public: - using Lattice = LatticeType; - using Base = DataflowAnalysis<Derived, Lattice, Dir>; - -private: - const CFG &Cfg; - AnalysisDeclContext &AC; - - /// The dataflow state before a basic block is processed. - llvm::DenseMap<const CFGBlock *, Lattice> InStates; - /// The dataflow state after a basic block is processed. - llvm::DenseMap<const CFGBlock *, Lattice> OutStates; - /// The dataflow state at a Program Point. - /// In a forward analysis, this is the state after the Fact at that point has - /// been applied, while in a backward analysis, it is the state before. - llvm::DenseMap<ProgramPoint, Lattice> PerPointStates; - - static constexpr bool isForward() { return Dir == Direction::Forward; } - -protected: - FactManager &FactMgr; - - explicit DataflowAnalysis(const CFG &Cfg, AnalysisDeclContext &AC, - FactManager &FactMgr) - : Cfg(Cfg), AC(AC), FactMgr(FactMgr) {} - -public: - void run() { - Derived &D = static_cast<Derived &>(*this); - llvm::TimeTraceScope Time(D.getAnalysisName()); - - using Worklist = - std::conditional_t<Dir == Direction::Forward, ForwardDataflowWorklist, - BackwardDataflowWorklist>; - Worklist W(Cfg, AC); - - const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit(); - InStates[Start] = D.getInitialState(); - W.enqueueBlock(Start); - - while (const CFGBlock *B = W.dequeue()) { - Lattice StateIn = *getInState(B); - Lattice StateOut = transferBlock(B, StateIn); - OutStates[B] = StateOut; - for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) { - if (!AdjacentB) - continue; - std::optional<Lattice> OldInState = getInState(AdjacentB); - Lattice NewInState = - !OldInState ? StateOut : D.join(*OldInState, StateOut); - // Enqueue the adjacent block if its in-state has changed or if we have - // never seen it. - if (!OldInState || NewInState != *OldInState) { - InStates[AdjacentB] = NewInState; - W.enqueueBlock(AdjacentB); - } - } - } - } - -protected: - Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); } - - std::optional<Lattice> getInState(const CFGBlock *B) const { - auto It = InStates.find(B); - if (It == InStates.end()) - return std::nullopt; - return It->second; - } - - Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); } - - void dump() const { - const Derived *D = static_cast<const Derived *>(this); - llvm::dbgs() << "==========================================\n"; - llvm::dbgs() << D->getAnalysisName() << " results:\n"; - llvm::dbgs() << "==========================================\n"; - const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry(); - getOutState(&B).dump(llvm::dbgs()); - } - -private: - /// Computes the state at one end of a block by applying all its facts - /// sequentially to a given state from the other end. - Lattice transferBlock(const CFGBlock *Block, Lattice State) { - auto Facts = FactMgr.getFacts(Block); - if constexpr (isForward()) { - for (const Fact *F : Facts) { - State = transferFact(State, F); - PerPointStates[F] = State; - } - } else { - for (const Fact *F : llvm::reverse(Facts)) { - // In backward analysis, capture the state before applying the fact. - PerPointStates[F] = State; - State = transferFact(State, F); - } - } - return State; - } - - Lattice transferFact(Lattice In, const Fact *F) { - assert(F); - Derived *D = static_cast<Derived *>(this); - switch (F->getKind()) { - case Fact::Kind::Issue: - return D->transfer(In, *F->getAs<IssueFact>()); - case Fact::Kind::Expire: - return D->transfer(In, *F->getAs<ExpireFact>()); - case Fact::Kind::OriginFlow: - return D->transfer(In, *F->getAs<OriginFlowFact>()); - case Fact::Kind::ReturnOfOrigin: - return D->transfer(In, *F->getAs<ReturnOfOriginFact>()); - case Fact::Kind::Use: - return D->transfer(In, *F->getAs<UseFact>()); - case Fact::Kind::TestPoint: - return D->transfer(In, *F->getAs<TestPointFact>()); - } - llvm_unreachable("Unknown fact kind"); - } - -public: - Lattice transfer(Lattice In, const IssueFact &) { return In; } - Lattice transfer(Lattice In, const ExpireFact &) { return In; } - Lattice transfer(Lattice In, const OriginFlowFact &) { return In; } - Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; } - Lattice transfer(Lattice In, const UseFact &) { return In; } - Lattice transfer(Lattice In, const TestPointFact &) { 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 deleted file mode 100644 index 1aea64f..0000000 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ /dev/null @@ -1,102 +0,0 @@ -//===- Facts.cpp - Lifetime Analysis Facts Implementation -------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/AST/Decl.h" -#include "clang/Analysis/Analyses/PostOrderCFGView.h" - -namespace clang::lifetimes::internal { - -void Fact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const { - OS << "Fact (Kind: " << static_cast<int>(K) << ")\n"; -} - -void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &OM) const { - OS << "Issue ("; - LM.getLoan(getLoanID()).dump(OS); - OS << ", ToOrigin: "; - OM.dump(getOriginID(), OS); - OS << ")\n"; -} - -void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM, - const OriginManager &) const { - OS << "Expire ("; - LM.getLoan(getLoanID()).dump(OS); - OS << ")\n"; -} - -void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { - OS << "OriginFlow (Dest: "; - OM.dump(getDestOriginID(), OS); - OS << ", Src: "; - OM.dump(getSrcOriginID(), OS); - OS << (getKillDest() ? "" : ", Merge"); - OS << ")\n"; -} - -void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { - OS << "ReturnOfOrigin ("; - OM.dump(getReturnedOriginID(), OS); - OS << ")\n"; -} - -void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &OM) const { - OS << "Use ("; - OM.dump(getUsedOrigin(OM), OS); - OS << ", " << (isWritten() ? "Write" : "Read") << ")\n"; -} - -void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &, - const OriginManager &) const { - OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n"; -} - -llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const { - llvm::StringMap<ProgramPoint> AnnotationToPointMap; - for (const CFGBlock *Block : BlockToFactsMap.keys()) { - for (const Fact *F : getFacts(Block)) { - if (const auto *TPF = F->getAs<TestPointFact>()) { - StringRef PointName = TPF->getAnnotation(); - assert(AnnotationToPointMap.find(PointName) == - AnnotationToPointMap.end() && - "more than one test points with the same name"); - AnnotationToPointMap[PointName] = F; - } - } - } - return AnnotationToPointMap; -} - -void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const { - llvm::dbgs() << "==========================================\n"; - llvm::dbgs() << " Lifetime Analysis Facts:\n"; - llvm::dbgs() << "==========================================\n"; - if (const Decl *D = AC.getDecl()) - if (const auto *ND = dyn_cast<NamedDecl>(D)) - llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n"; - // Print blocks in the order as they appear in code for a stable ordering. - for (const CFGBlock *B : *AC.getAnalysis<PostOrderCFGView>()) { - llvm::dbgs() << " Block B" << B->getBlockID() << ":\n"; - auto It = BlockToFactsMap.find(B); - if (It != BlockToFactsMap.end()) { - for (const Fact *F : It->second) { - llvm::dbgs() << " "; - F->dump(llvm::dbgs(), LoanMgr, OriginMgr); - } - } - llvm::dbgs() << " End of Block\n"; - } -} - -} // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp deleted file mode 100644 index 485308f..0000000 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ /dev/null @@ -1,348 +0,0 @@ -//===- FactsGenerator.cpp - Lifetime Facts Generation -----------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" -#include "clang/Analysis/Analyses/PostOrderCFGView.h" -#include "llvm/Support/TimeProfiler.h" - -namespace clang::lifetimes::internal { - -static bool isGslPointerType(QualType QT) { - if (const auto *RD = QT->getAsCXXRecordDecl()) { - // We need to check the template definition for specializations. - if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) - return CTSD->getSpecializedTemplate() - ->getTemplatedDecl() - ->hasAttr<PointerAttr>(); - return RD->hasAttr<PointerAttr>(); - } - return false; -} - -static bool isPointerType(QualType QT) { - return QT->isPointerOrReferenceType() || isGslPointerType(QT); -} -// Check if a type has an origin. -static bool hasOrigin(const Expr *E) { - return E->isGLValue() || isPointerType(E->getType()); -} - -static bool hasOrigin(const VarDecl *VD) { - return isPointerType(VD->getType()); -} - -/// Creates a loan for the storage path of a given declaration reference. -/// This function should be called whenever a DeclRefExpr represents a borrow. -/// \param DRE The declaration reference expression that initiates the borrow. -/// \return The new Loan on success, nullptr otherwise. -static const Loan *createLoan(FactManager &FactMgr, const DeclRefExpr *DRE) { - if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) { - AccessPath Path(VD); - // The loan is created at the location of the DeclRefExpr. - return &FactMgr.getLoanMgr().addLoan(Path, DRE); - } - return nullptr; -} - -void FactsGenerator::run() { - llvm::TimeTraceScope TimeProfile("FactGenerator"); - // Iterate through the CFG blocks in reverse post-order to ensure that - // initializations and destructions are processed in the correct sequence. - for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) { - CurrentBlockFacts.clear(); - for (unsigned I = 0; I < Block->size(); ++I) { - const CFGElement &Element = Block->Elements[I]; - if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) - Visit(CS->getStmt()); - else if (std::optional<CFGAutomaticObjDtor> DtorOpt = - Element.getAs<CFGAutomaticObjDtor>()) - handleDestructor(*DtorOpt); - } - FactMgr.addBlockFacts(Block, CurrentBlockFacts); - } -} - -void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) { - for (const Decl *D : DS->decls()) - if (const auto *VD = dyn_cast<VarDecl>(D)) - if (hasOrigin(VD)) - if (const Expr *InitExpr = VD->getInit()) - killAndFlowOrigin(*VD, *InitExpr); -} - -void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) { - handleUse(DRE); - // For non-pointer/non-view types, a reference to the variable's storage - // is a borrow. We create a loan for it. - // For pointer/view types, we stick to the existing model for now and do - // not create an extra origin for the l-value expression itself. - - // TODO: A single origin for a `DeclRefExpr` for a pointer or view type is - // not sufficient to model the different levels of indirection. The current - // single-origin model cannot distinguish between a loan to the variable's - // storage and a loan to what it points to. A multi-origin model would be - // required for this. - if (!isPointerType(DRE->getType())) { - if (const Loan *L = createLoan(FactMgr, DRE)) { - OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE); - CurrentBlockFacts.push_back( - FactMgr.createFact<IssueFact>(L->ID, ExprOID)); - } - } -} - -void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) { - if (isGslPointerType(CCE->getType())) { - handleGSLPointerConstruction(CCE); - return; - } -} - -void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { - // Specifically for conversion operators, - // like `std::string_view p = std::string{};` - if (isGslPointerType(MCE->getType()) && - isa<CXXConversionDecl>(MCE->getCalleeDecl())) { - // The argument is the implicit object itself. - handleFunctionCall(MCE, MCE->getMethodDecl(), - {MCE->getImplicitObjectArgument()}, - /*IsGslConstruction=*/true); - } - if (const CXXMethodDecl *Method = MCE->getMethodDecl()) { - // Construct the argument list, with the implicit 'this' object as the - // first argument. - llvm::SmallVector<const Expr *, 4> Args; - Args.push_back(MCE->getImplicitObjectArgument()); - Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs()); - - handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false); - } -} - -void FactsGenerator::VisitCallExpr(const CallExpr *CE) { - handleFunctionCall(CE, CE->getDirectCallee(), - {CE->getArgs(), CE->getNumArgs()}); -} - -void FactsGenerator::VisitCXXNullPtrLiteralExpr( - const CXXNullPtrLiteralExpr *N) { - /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized - /// pointers can use the same type of loan. - FactMgr.getOriginMgr().getOrCreate(*N); -} - -void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) { - if (!hasOrigin(ICE)) - return; - // An ImplicitCastExpr node itself gets an origin, which flows from the - // origin of its sub-expression (after stripping its own parens/casts). - killAndFlowOrigin(*ICE, *ICE->getSubExpr()); -} - -void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) { - if (UO->getOpcode() == UO_AddrOf) { - const Expr *SubExpr = UO->getSubExpr(); - // Taking address of a pointer-type expression is not yet supported and - // will be supported in multi-origin model. - if (isPointerType(SubExpr->getType())) - return; - // The origin of an address-of expression (e.g., &x) is the origin of - // its sub-expression (x). This fact will cause the dataflow analysis - // to propagate any loans held by the sub-expression's origin to the - // origin of this UnaryOperator expression. - killAndFlowOrigin(*UO, *SubExpr); - } -} - -void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) { - if (const Expr *RetExpr = RS->getRetValue()) { - if (hasOrigin(RetExpr)) { - OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr); - CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID)); - } - } -} - -void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { - if (BO->isAssignmentOp()) - handleAssignment(BO->getLHS(), BO->getRHS()); -} - -void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { - // Assignment operators have special "kill-then-propagate" semantics - // and are handled separately. - if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) { - handleAssignment(OCE->getArg(0), OCE->getArg(1)); - return; - } - handleFunctionCall(OCE, OCE->getDirectCallee(), - {OCE->getArgs(), OCE->getNumArgs()}, - /*IsGslConstruction=*/false); -} - -void FactsGenerator::VisitCXXFunctionalCastExpr( - const CXXFunctionalCastExpr *FCE) { - // Check if this is a test point marker. If so, we are done with this - // expression. - if (handleTestPoint(FCE)) - return; - if (isGslPointerType(FCE->getType())) - killAndFlowOrigin(*FCE, *FCE->getSubExpr()); -} - -void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) { - if (!hasOrigin(ILE)) - return; - // For list initialization with a single element, like `View{...}`, the - // origin of the list itself is the origin of its single element. - if (ILE->getNumInits() == 1) - killAndFlowOrigin(*ILE, *ILE->getInit(0)); -} - -void FactsGenerator::VisitMaterializeTemporaryExpr( - const MaterializeTemporaryExpr *MTE) { - if (!hasOrigin(MTE)) - return; - // A temporary object's origin is the same as the origin of the - // expression that initializes it. - killAndFlowOrigin(*MTE, *MTE->getSubExpr()); -} - -void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) { - /// TODO: Also handle trivial destructors (e.g., for `int` - /// variables) which will never have a CFGAutomaticObjDtor node. - /// TODO: Handle loans to temporaries. - /// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the - /// lifetime ends. - const VarDecl *DestructedVD = DtorOpt.getVarDecl(); - if (!DestructedVD) - return; - // Iterate through all loans to see if any expire. - /// TODO(opt): Do better than a linear search to find loans associated with - /// 'DestructedVD'. - for (const Loan &L : FactMgr.getLoanMgr().getLoans()) { - const AccessPath &LoanPath = L.Path; - // Check if the loan is for a stack variable and if that variable - // is the one being destructed. - if (LoanPath.D == DestructedVD) - CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>( - L.ID, DtorOpt.getTriggerStmt()->getEndLoc())); - } -} - -void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { - assert(isGslPointerType(CCE->getType())); - if (CCE->getNumArgs() != 1) - return; - if (hasOrigin(CCE->getArg(0))) - killAndFlowOrigin(*CCE, *CCE->getArg(0)); - else - // This could be a new borrow. - handleFunctionCall(CCE, CCE->getConstructor(), - {CCE->getArgs(), CCE->getNumArgs()}, - /*IsGslConstruction=*/true); -} - -/// Checks if a call-like expression creates a borrow by passing a value to a -/// reference parameter, creating an IssueFact if it does. -/// \param IsGslConstruction True if this is a GSL construction where all -/// argument origins should flow to the returned origin. -void FactsGenerator::handleFunctionCall(const Expr *Call, - const FunctionDecl *FD, - ArrayRef<const Expr *> Args, - bool IsGslConstruction) { - // Ignore functions returning values with no origin. - if (!FD || !hasOrigin(Call)) - return; - auto IsArgLifetimeBound = [FD](unsigned I) -> bool { - const ParmVarDecl *PVD = nullptr; - if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); - Method && Method->isInstance()) { - if (I == 0) - // For the 'this' argument, the attribute is on the method itself. - return implicitObjectParamIsLifetimeBound(Method); - if ((I - 1) < Method->getNumParams()) - // For explicit arguments, find the corresponding parameter - // declaration. - PVD = Method->getParamDecl(I - 1); - } else if (I < FD->getNumParams()) - // For free functions or static methods. - PVD = FD->getParamDecl(I); - return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false; - }; - if (Args.empty()) - return; - bool killedSrc = false; - for (unsigned I = 0; I < Args.size(); ++I) - if (IsGslConstruction || IsArgLifetimeBound(I)) { - if (!killedSrc) { - killedSrc = true; - killAndFlowOrigin(*Call, *Args[I]); - } else - flowOrigin(*Call, *Args[I]); - } -} - -/// Checks if the expression is a `void("__lifetime_test_point_...")` cast. -/// If so, creates a `TestPointFact` and returns true. -bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) { - if (!FCE->getType()->isVoidType()) - return false; - - const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts(); - if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) { - llvm::StringRef LiteralValue = SL->getString(); - const std::string Prefix = "__lifetime_test_point_"; - - if (LiteralValue.starts_with(Prefix)) { - StringRef Annotation = LiteralValue.drop_front(Prefix.length()); - CurrentBlockFacts.push_back( - FactMgr.createFact<TestPointFact>(Annotation)); - return true; - } - } - return false; -} - -void FactsGenerator::handleAssignment(const Expr *LHSExpr, - const Expr *RHSExpr) { - if (!hasOrigin(LHSExpr)) - return; - // Find the underlying variable declaration for the left-hand side. - if (const auto *DRE_LHS = - dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) { - markUseAsWrite(DRE_LHS); - if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) { - // Kill the old loans of the destination origin and flow the new loans - // from the source origin. - killAndFlowOrigin(*VD_LHS, *RHSExpr); - } - } -} - -// 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) { - if (isPointerType(DRE->getType())) { - UseFact *UF = FactMgr.createFact<UseFact>(DRE); - CurrentBlockFacts.push_back(UF); - assert(!UseFacts.contains(DRE)); - UseFacts[DRE] = UF; - } -} - -void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) { - if (!isPointerType(DRE->getType())) - return; - assert(UseFacts.contains(DRE)); - UseFacts[DRE]->markAsWritten(); -} - -} // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp deleted file mode 100644 index 00c7ed90..0000000 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp +++ /dev/null @@ -1,77 +0,0 @@ -//===- LifetimeSafety.cpp - C++ Lifetime Safety Analysis -*--------- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file implements the main LifetimeSafetyAnalysis class, which coordinates -// the various components (fact generation, loan propagation, live origins -// analysis, and checking) to detect lifetime safety violations in C++ code. -// -//===----------------------------------------------------------------------===// -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" -#include "clang/AST/Decl.h" -#include "clang/AST/Expr.h" -#include "clang/AST/Type.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h" -#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" -#include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Analysis/CFG.h" -#include "llvm/ADT/FoldingSet.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/ErrorHandling.h" -#include "llvm/Support/TimeProfiler.h" -#include <memory> - -namespace clang::lifetimes { -namespace internal { - -LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC, - LifetimeSafetyReporter *Reporter) - : AC(AC), Reporter(Reporter) {} - -void LifetimeSafetyAnalysis::run() { - llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis"); - - const CFG &Cfg = *AC.getCFG(); - DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(), - /*ShowColors=*/true)); - - FactsGenerator FactGen(FactMgr, AC); - FactGen.run(); - DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC)); - - /// TODO(opt): Consider optimizing individual blocks before running the - /// dataflow analysis. - /// 1. Expression Origins: These are assigned once and read at most once, - /// forming simple chains. These chains can be compressed into a single - /// assignment. - /// 2. Block-Local Loans: Origins of expressions are never read by other - /// blocks; only Decls are visible. Therefore, loans in a block that - /// never reach an Origin associated with a Decl can be safely dropped by - /// the analysis. - /// 3. Collapse ExpireFacts belonging to same source location into a single - /// Fact. - LoanPropagation = std::make_unique<LoanPropagationAnalysis>( - Cfg, AC, FactMgr, Factory.OriginMapFactory, Factory.LoanSetFactory); - - LiveOrigins = std::make_unique<LiveOriginsAnalysis>( - Cfg, AC, FactMgr, Factory.LivenessMapFactory); - DEBUG_WITH_TYPE("LiveOrigins", - LiveOrigins->dump(llvm::dbgs(), FactMgr.getTestPoints())); - - runLifetimeChecker(*LoanPropagation, *LiveOrigins, FactMgr, AC, Reporter); -} -} // namespace internal - -void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, - LifetimeSafetyReporter *Reporter) { - internal::LifetimeSafetyAnalysis Analysis(AC, Reporter); - Analysis.run(); -} -} // namespace clang::lifetimes diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp deleted file mode 100644 index cddb3f3c..0000000 --- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp +++ /dev/null @@ -1,180 +0,0 @@ -//===- LiveOrigins.cpp - Live Origins Analysis -----------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" -#include "Dataflow.h" -#include "llvm/Support/ErrorHandling.h" - -namespace clang::lifetimes::internal { -namespace { - -/// The dataflow lattice for origin liveness analysis. -/// It tracks which origins are live, why they're live (which UseFact), -/// and the confidence level of that liveness. -struct Lattice { - LivenessMap LiveOrigins; - - Lattice() : LiveOrigins(nullptr) {}; - - explicit Lattice(LivenessMap L) : LiveOrigins(L) {} - - bool operator==(const Lattice &Other) const { - return LiveOrigins == Other.LiveOrigins; - } - - bool operator!=(const Lattice &Other) const { return !(*this == Other); } - - void dump(llvm::raw_ostream &OS, const OriginManager &OM) const { - if (LiveOrigins.isEmpty()) - OS << " <empty>\n"; - for (const auto &Entry : LiveOrigins) { - OriginID OID = Entry.first; - const LivenessInfo &Info = Entry.second; - OS << " "; - OM.dump(OID, OS); - OS << " is "; - switch (Info.Kind) { - case LivenessKind::Must: - OS << "definitely"; - break; - case LivenessKind::Maybe: - OS << "maybe"; - break; - case LivenessKind::Dead: - llvm_unreachable("liveness kind of live origins should not be dead."); - } - OS << " live at this point\n"; - } - } -}; - -/// The analysis that tracks which origins are live, with granular information -/// about the causing use fact and confidence level. This is a backward -/// analysis. -class AnalysisImpl - : public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Backward> { - -public: - AnalysisImpl(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - LivenessMap::Factory &SF) - : DataflowAnalysis(C, AC, F), FactMgr(F), Factory(SF) {} - using DataflowAnalysis<AnalysisImpl, Lattice, Direction::Backward>::transfer; - - StringRef getAnalysisName() const { return "LiveOrigins"; } - - Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); } - - /// Merges two lattices by combining liveness information. - /// When the same origin has different confidence levels, we take the lower - /// one. - Lattice join(Lattice L1, Lattice L2) const { - LivenessMap Merged = L1.LiveOrigins; - // Take the earliest UseFact to make the join hermetic and commutative. - auto CombineUseFact = [](const UseFact &A, - const UseFact &B) -> const UseFact * { - return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A - : &B; - }; - auto CombineLivenessKind = [](LivenessKind K1, - LivenessKind K2) -> LivenessKind { - assert(K1 != LivenessKind::Dead && "LivenessKind should not be dead."); - assert(K2 != LivenessKind::Dead && "LivenessKind should not be dead."); - // Only return "Must" if both paths are "Must", otherwise Maybe. - if (K1 == LivenessKind::Must && K2 == LivenessKind::Must) - return LivenessKind::Must; - return LivenessKind::Maybe; - }; - auto CombineLivenessInfo = [&](const LivenessInfo *L1, - const LivenessInfo *L2) -> LivenessInfo { - assert((L1 || L2) && "unexpectedly merging 2 empty sets"); - if (!L1) - return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe); - if (!L2) - return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe); - return LivenessInfo( - CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact), - CombineLivenessKind(L1->Kind, L2->Kind)); - }; - return Lattice(utils::join( - L1.LiveOrigins, L2.LiveOrigins, Factory, CombineLivenessInfo, - // A symmetric join is required here. If an origin is live on one - // branch but not the other, its confidence must be demoted to `Maybe`. - utils::JoinKind::Symmetric)); - } - - /// A read operation makes the origin live with definite confidence, as it - /// dominates this program point. A write operation kills the liveness of - /// the origin since it overwrites the value. - Lattice transfer(Lattice In, const UseFact &UF) { - OriginID OID = UF.getUsedOrigin(FactMgr.getOriginMgr()); - // Write kills liveness. - if (UF.isWritten()) - return Lattice(Factory.remove(In.LiveOrigins, OID)); - // Read makes origin live with definite confidence (dominates this point). - return Lattice(Factory.add(In.LiveOrigins, OID, - LivenessInfo(&UF, LivenessKind::Must))); - } - - /// Issuing a new loan to an origin kills its liveness. - Lattice transfer(Lattice In, const IssueFact &IF) { - return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID())); - } - - /// An OriginFlow kills the liveness of the destination origin if `KillDest` - /// is true. Otherwise, it propagates liveness from destination to source. - Lattice transfer(Lattice In, const OriginFlowFact &OF) { - if (!OF.getKillDest()) - return In; - return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID())); - } - - LivenessMap getLiveOriginsAt(ProgramPoint P) const { - return getState(P).LiveOrigins; - } - - // Dump liveness values on all test points in the program. - void dump(llvm::raw_ostream &OS, - llvm::StringMap<ProgramPoint> TestPoints) const { - llvm::dbgs() << "==========================================\n"; - llvm::dbgs() << getAnalysisName() << " results:\n"; - llvm::dbgs() << "==========================================\n"; - for (const auto &Entry : TestPoints) { - OS << "TestPoint: " << Entry.getKey() << "\n"; - getState(Entry.getValue()).dump(OS, FactMgr.getOriginMgr()); - } - } - -private: - FactManager &FactMgr; - LivenessMap::Factory &Factory; -}; -} // namespace - -// PImpl wrapper implementation -class LiveOriginsAnalysis::Impl : public AnalysisImpl { - using AnalysisImpl::AnalysisImpl; -}; - -LiveOriginsAnalysis::LiveOriginsAnalysis(const CFG &C, AnalysisDeclContext &AC, - FactManager &F, - LivenessMap::Factory &SF) - : PImpl(std::make_unique<Impl>(C, AC, F, SF)) { - PImpl->run(); -} - -LiveOriginsAnalysis::~LiveOriginsAnalysis() = default; - -LivenessMap LiveOriginsAnalysis::getLiveOriginsAt(ProgramPoint P) const { - return PImpl->getLiveOriginsAt(P); -} - -void LiveOriginsAnalysis::dump(llvm::raw_ostream &OS, - llvm::StringMap<ProgramPoint> TestPoints) const { - PImpl->dump(OS, TestPoints); -} -} // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp deleted file mode 100644 index 387097e..0000000 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ /dev/null @@ -1,138 +0,0 @@ -//===- LoanPropagation.cpp - Loan Propagation Analysis ---------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" -#include "Dataflow.h" -#include <memory> - -namespace clang::lifetimes::internal { -namespace { -/// Represents the dataflow lattice for loan propagation. -/// -/// This lattice tracks which loans each origin may hold at a given program -/// point.The lattice has a finite height: An origin's loan set is bounded by -/// the total number of loans in the function. -/// TODO(opt): To reduce the lattice size, propagate origins of declarations, -/// not expressions, because expressions are not visible across blocks. -struct Lattice { - /// The map from an origin to the set of loans it contains. - OriginLoanMap Origins = OriginLoanMap(nullptr); - - explicit Lattice(const OriginLoanMap &S) : Origins(S) {} - Lattice() = default; - - bool operator==(const Lattice &Other) const { - return Origins == Other.Origins; - } - bool operator!=(const Lattice &Other) const { return !(*this == Other); } - - void dump(llvm::raw_ostream &OS) const { - OS << "LoanPropagationLattice State:\n"; - if (Origins.isEmpty()) - OS << " <empty>\n"; - for (const auto &Entry : Origins) { - if (Entry.second.isEmpty()) - OS << " Origin " << Entry.first << " contains no loans\n"; - for (const LoanID &LID : Entry.second) - OS << " Origin " << Entry.first << " contains Loan " << LID << "\n"; - } - } -}; - -class AnalysisImpl - : public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Forward> { -public: - AnalysisImpl(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - OriginLoanMap::Factory &OriginLoanMapFactory, - LoanSet::Factory &LoanSetFactory) - : DataflowAnalysis(C, AC, F), OriginLoanMapFactory(OriginLoanMapFactory), - LoanSetFactory(LoanSetFactory) {} - - using Base::transfer; - - StringRef getAnalysisName() const { return "LoanPropagation"; } - - Lattice getInitialState() { return Lattice{}; } - - /// Merges two lattices by taking the union of loans for each origin. - // TODO(opt): Keep the state small by removing origins which become dead. - Lattice join(Lattice A, Lattice B) { - OriginLoanMap JoinedOrigins = utils::join( - A.Origins, B.Origins, OriginLoanMapFactory, - [&](const LoanSet *S1, const LoanSet *S2) { - assert((S1 || S2) && "unexpectedly merging 2 empty sets"); - if (!S1) - return *S2; - if (!S2) - return *S1; - return utils::join(*S1, *S2, LoanSetFactory); - }, - // Asymmetric join is a performance win. For origins present only on one - // branch, the loan set can be carried over as-is. - utils::JoinKind::Asymmetric); - return Lattice(JoinedOrigins); - } - - /// A new loan is issued to the origin. Old loans are erased. - Lattice transfer(Lattice In, const IssueFact &F) { - OriginID OID = F.getOriginID(); - LoanID LID = F.getLoanID(); - return Lattice(OriginLoanMapFactory.add( - In.Origins, OID, - LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID))); - } - - /// A flow from source to destination. If `KillDest` is true, this replaces - /// the destination's loans with the source's. Otherwise, the source's loans - /// are merged into the destination's. - Lattice transfer(Lattice In, const OriginFlowFact &F) { - OriginID DestOID = F.getDestOriginID(); - OriginID SrcOID = F.getSrcOriginID(); - - LoanSet DestLoans = - F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID); - LoanSet SrcLoans = getLoans(In, SrcOID); - LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory); - - return Lattice(OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans)); - } - - LoanSet getLoans(OriginID OID, ProgramPoint P) const { - return getLoans(getState(P), OID); - } - -private: - LoanSet getLoans(Lattice L, OriginID OID) const { - if (auto *Loans = L.Origins.lookup(OID)) - return *Loans; - return LoanSetFactory.getEmptySet(); - } - - OriginLoanMap::Factory &OriginLoanMapFactory; - LoanSet::Factory &LoanSetFactory; -}; -} // namespace - -class LoanPropagationAnalysis::Impl final : public AnalysisImpl { - using AnalysisImpl::AnalysisImpl; -}; - -LoanPropagationAnalysis::LoanPropagationAnalysis( - const CFG &C, AnalysisDeclContext &AC, FactManager &F, - OriginLoanMap::Factory &OriginLoanMapFactory, - LoanSet::Factory &LoanSetFactory) - : PImpl(std::make_unique<Impl>(C, AC, F, OriginLoanMapFactory, - LoanSetFactory)) { - PImpl->run(); -} - -LoanPropagationAnalysis::~LoanPropagationAnalysis() = default; - -LoanSet LoanPropagationAnalysis::getLoans(OriginID OID, ProgramPoint P) const { - return PImpl->getLoans(OID, P); -} -} // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp deleted file mode 100644 index 2c85a3c..0000000 --- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp +++ /dev/null @@ -1,18 +0,0 @@ -//===- Loans.cpp - Loan Implementation --------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h" - -namespace clang::lifetimes::internal { - -void Loan::dump(llvm::raw_ostream &OS) const { - OS << ID << " (Path: "; - OS << Path.D->getNameAsString() << ")"; -} - -} // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp deleted file mode 100644 index ea51a75..0000000 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ /dev/null @@ -1,89 +0,0 @@ -//===- Origins.cpp - Origin Implementation -----------------------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" - -namespace clang::lifetimes::internal { - -void OriginManager::dump(OriginID OID, llvm::raw_ostream &OS) const { - OS << OID << " ("; - Origin O = getOrigin(OID); - if (const ValueDecl *VD = O.getDecl()) - OS << "Decl: " << VD->getNameAsString(); - else if (const Expr *E = O.getExpr()) - OS << "Expr: " << E->getStmtClassName(); - else - OS << "Unknown"; - OS << ")"; -} - -Origin &OriginManager::addOrigin(OriginID ID, const clang::ValueDecl &D) { - AllOrigins.emplace_back(ID, &D); - return AllOrigins.back(); -} - -Origin &OriginManager::addOrigin(OriginID ID, const clang::Expr &E) { - AllOrigins.emplace_back(ID, &E); - return AllOrigins.back(); -} - -// TODO: Mark this method as const once we remove the call to getOrCreate. -OriginID OriginManager::get(const Expr &E) { - auto It = ExprToOriginID.find(&E); - if (It != ExprToOriginID.end()) - return It->second; - // If the expression itself has no specific origin, and it's a reference - // to a declaration, its origin is that of the declaration it refers to. - // For pointer types, where we don't pre-emptively create an origin for the - // DeclRefExpr itself. - if (const auto *DRE = dyn_cast<DeclRefExpr>(&E)) - return get(*DRE->getDecl()); - // TODO: This should be an assert(It != ExprToOriginID.end()). The current - // implementation falls back to getOrCreate to avoid crashing on - // yet-unhandled pointer expressions, creating an empty origin for them. - return getOrCreate(E); -} - -OriginID OriginManager::get(const ValueDecl &D) { - auto It = DeclToOriginID.find(&D); - // TODO: This should be an assert(It != DeclToOriginID.end()). The current - // implementation falls back to getOrCreate to avoid crashing on - // yet-unhandled pointer expressions, creating an empty origin for them. - if (It == DeclToOriginID.end()) - return getOrCreate(D); - - return It->second; -} - -OriginID OriginManager::getOrCreate(const Expr &E) { - auto It = ExprToOriginID.find(&E); - if (It != ExprToOriginID.end()) - return It->second; - - OriginID NewID = getNextOriginID(); - addOrigin(NewID, E); - ExprToOriginID[&E] = NewID; - return NewID; -} - -const Origin &OriginManager::getOrigin(OriginID ID) const { - assert(ID.Value < AllOrigins.size()); - return AllOrigins[ID.Value]; -} - -OriginID OriginManager::getOrCreate(const ValueDecl &D) { - auto It = DeclToOriginID.find(&D); - if (It != DeclToOriginID.end()) - return It->second; - OriginID NewID = getNextOriginID(); - addOrigin(NewID, D); - DeclToOriginID[&D] = NewID; - return NewID; -} - -} // namespace clang::lifetimes::internal diff --git a/clang/lib/Basic/Targets.cpp b/clang/lib/Basic/Targets.cpp index d9aafc6..b7e8bad 100644 --- a/clang/lib/Basic/Targets.cpp +++ b/clang/lib/Basic/Targets.cpp @@ -704,6 +704,10 @@ std::unique_ptr<TargetInfo> AllocateTarget(const llvm::Triple &Triple, case llvm::Triple::Emscripten: return std::make_unique<EmscriptenTargetInfo<WebAssembly32TargetInfo>>( Triple, Opts); + + case llvm::Triple::Linux: + return std::make_unique<WALITargetInfo<WebAssembly32TargetInfo>>(Triple, + Opts); case llvm::Triple::UnknownOS: return std::make_unique<WebAssemblyOSTargetInfo<WebAssembly32TargetInfo>>( Triple, Opts); diff --git a/clang/lib/Basic/Targets/OSTargets.h b/clang/lib/Basic/Targets/OSTargets.h index 6c49a09..bd6ffcf 100644 --- a/clang/lib/Basic/Targets/OSTargets.h +++ b/clang/lib/Basic/Targets/OSTargets.h @@ -948,6 +948,23 @@ public: using WebAssemblyOSTargetInfo<Target>::WebAssemblyOSTargetInfo; }; +// WALI target +template <typename Target> +class LLVM_LIBRARY_VISIBILITY WALITargetInfo + : public WebAssemblyOSTargetInfo<Target> { + void getOSDefines(const LangOptions &Opts, const llvm::Triple &Triple, + MacroBuilder &Builder) const final { + WebAssemblyOSTargetInfo<Target>::getOSDefines(Opts, Triple, Builder); + // Linux defines; list based off of gcc output + DefineStd(Builder, "unix", Opts); + DefineStd(Builder, "linux", Opts); + Builder.defineMacro("__wali__"); + } + +public: + using WebAssemblyOSTargetInfo<Target>::WebAssemblyOSTargetInfo; +}; + // Emscripten target template <typename Target> class LLVM_LIBRARY_VISIBILITY EmscriptenTargetInfo diff --git a/clang/lib/Basic/Targets/WebAssembly.h b/clang/lib/Basic/Targets/WebAssembly.h index eba7422..4de6ce6 100644 --- a/clang/lib/Basic/Targets/WebAssembly.h +++ b/clang/lib/Basic/Targets/WebAssembly.h @@ -88,12 +88,23 @@ public: LongDoubleWidth = LongDoubleAlign = 128; LongDoubleFormat = &llvm::APFloat::IEEEquad(); MaxAtomicPromoteWidth = MaxAtomicInlineWidth = 64; - // size_t being unsigned long for both wasm32 and wasm64 makes mangled names - // more consistent between the two. - SizeType = UnsignedLong; - PtrDiffType = SignedLong; - IntPtrType = SignedLong; HasUnalignedAccess = true; + if (T.isWALI()) { + // The WALI ABI is documented here: + // https://doc.rust-lang.org/rustc/platform-support/wasm32-wali-linux.html + // Currently, this ABI only applies to wasm32 targets and notably requires + // 64-bit longs + LongAlign = LongWidth = 64; + SizeType = UnsignedInt; + PtrDiffType = SignedInt; + IntPtrType = SignedInt; + } else { + // size_t being unsigned long for both wasm32 and wasm64 makes mangled + // names more consistent between the two. + SizeType = UnsignedLong; + PtrDiffType = SignedLong; + IntPtrType = SignedLong; + } } StringRef getABI() const override; diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h index 25afe8b..a6f10e6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h +++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h @@ -319,12 +319,6 @@ public: return cir::ConstantOp::create(*this, loc, cir::IntAttr::get(sInt64Ty, c)); } - // Creates constant nullptr for pointer type ty. - cir::ConstantOp getNullPtr(mlir::Type ty, mlir::Location loc) { - assert(!cir::MissingFeatures::targetCodeGenInfoGetNullPointer()); - return cir::ConstantOp::create(*this, loc, getConstPtrAttr(ty, 0)); - } - mlir::Value createNeg(mlir::Value value) { if (auto intTy = mlir::dyn_cast<cir::IntType>(value.getType())) { diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp index 6453843..f9ff37b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenException.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -64,3 +64,11 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) { // Deactivate the cleanup block. assert(!cir::MissingFeatures::ehCleanupScope()); } + +mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { + if (s.getTryBlock()->body_empty()) + return mlir::LogicalResult::success(); + + cgm.errorNYI("exitCXXTryStmt: CXXTryStmt with non-empty body"); + return mlir::LogicalResult::success(); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 7a606ee..d71de2f 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1297,6 +1297,8 @@ public: void emitCXXThrowExpr(const CXXThrowExpr *e); + mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s); + void emitCtorPrologue(const clang::CXXConstructorDecl *ctor, clang::CXXCtorType ctorType, FunctionArgList &args); diff --git a/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp b/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp index 4cf2237..5ba6bcb 100644 --- a/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenOpenACC.cpp @@ -73,7 +73,7 @@ CIRGenFunction::getOpenACCDataOperandInfo(const Expr *e) { // Array sections are special, and we have to treat them that way. if (const auto *section = dyn_cast<ArraySectionExpr>(curVarExpr->IgnoreParenImpCasts())) - origType = ArraySectionExpr::getBaseOriginalType(section); + origType = section->getElementType(); mlir::Location exprLoc = cgm.getLoc(curVarExpr->getBeginLoc()); llvm::SmallVector<mlir::Value> bounds; @@ -84,16 +84,10 @@ CIRGenFunction::getOpenACCDataOperandInfo(const Expr *e) { e->printPretty(os, nullptr, getContext().getPrintingPolicy()); auto addBoundType = [&](const Expr *e) { - if (const auto *section = dyn_cast<ArraySectionExpr>(curVarExpr)) { - QualType baseTy = ArraySectionExpr::getBaseOriginalType( - section->getBase()->IgnoreParenImpCasts()); - if (auto *at = getContext().getAsArrayType(baseTy)) - boundTypes.push_back(at->getElementType()); - else - boundTypes.push_back(baseTy->getPointeeType()); - } else { + if (const auto *section = dyn_cast<ArraySectionExpr>(curVarExpr)) + boundTypes.push_back(section->getElementType()); + else boundTypes.push_back(curVarExpr->getType()); - } }; addBoundType(curVarExpr); @@ -113,8 +107,7 @@ CIRGenFunction::getOpenACCDataOperandInfo(const Expr *e) { if (const Expr *len = section->getLength()) { extent = emitOpenACCIntExpr(len); } else { - QualType baseTy = ArraySectionExpr::getBaseOriginalType( - section->getBase()->IgnoreParenImpCasts()); + QualType baseTy = section->getBaseType(); // We know this is the case as implicit lengths are only allowed for // array types with a constant size, or a dependent size. AND since // we are codegen we know we're not dependent. diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index 0b8f8bf..cfd48a2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -154,6 +154,8 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, return emitWhileStmt(cast<WhileStmt>(*s)); case Stmt::DoStmtClass: return emitDoStmt(cast<DoStmt>(*s)); + case Stmt::CXXTryStmtClass: + return emitCXXTryStmt(cast<CXXTryStmt>(*s)); case Stmt::CXXForRangeStmtClass: return emitCXXForRangeStmt(cast<CXXForRangeStmt>(*s), attr); case Stmt::OpenACCComputeConstructClass: @@ -199,7 +201,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, case Stmt::CoroutineBodyStmtClass: return emitCoroutineBody(cast<CoroutineBodyStmt>(*s)); case Stmt::CoreturnStmtClass: - case Stmt::CXXTryStmtClass: case Stmt::IndirectGotoStmtClass: case Stmt::OMPParallelDirectiveClass: case Stmt::OMPTaskwaitDirectiveClass: diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt index df7a1a3..3fc5b06 100644 --- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt +++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_library(MLIRCIRTransforms FlattenCFG.cpp HoistAllocas.cpp LoweringPrepare.cpp + LoweringPrepareItaniumCXXABI.cpp GotoSolver.cpp DEPENDS diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 706e54f..dbff0b9 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "LoweringPrepareCXXABI.h" #include "PassDetail.h" #include "clang/AST/ASTContext.h" #include "clang/Basic/Module.h" @@ -62,6 +63,7 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> { void lowerComplexMulOp(cir::ComplexMulOp op); void lowerUnaryOp(cir::UnaryOp op); void lowerGlobalOp(cir::GlobalOp op); + void lowerDynamicCastOp(cir::DynamicCastOp op); void lowerArrayDtor(cir::ArrayDtor op); void lowerArrayCtor(cir::ArrayCtor op); @@ -91,6 +93,9 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> { clang::ASTContext *astCtx; + // Helper for lowering C++ ABI specific operations. + std::shared_ptr<cir::LoweringPrepareCXXABI> cxxABI; + /// Tracks current module. mlir::ModuleOp mlirModule; @@ -101,7 +106,24 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> { /// List of ctors and their priorities to be called before main() llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList; - void setASTContext(clang::ASTContext *c) { astCtx = c; } + void setASTContext(clang::ASTContext *c) { + astCtx = c; + switch (c->getCXXABIKind()) { + case clang::TargetCXXABI::GenericItanium: + // We'll need X86-specific support for handling vaargs lowering, but for + // now the Itanium ABI will work. + assert(!cir::MissingFeatures::loweringPrepareX86CXXABI()); + cxxABI.reset(cir::LoweringPrepareCXXABI::createItaniumABI()); + break; + case clang::TargetCXXABI::GenericAArch64: + case clang::TargetCXXABI::AppleARM64: + assert(!cir::MissingFeatures::loweringPrepareAArch64XXABI()); + cxxABI.reset(cir::LoweringPrepareCXXABI::createItaniumABI()); + break; + default: + llvm_unreachable("NYI"); + } + } }; } // namespace @@ -850,6 +872,17 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { cir::ReturnOp::create(builder, f.getLoc()); } +void LoweringPreparePass::lowerDynamicCastOp(DynamicCastOp op) { + CIRBaseBuilderTy builder(getContext()); + builder.setInsertionPointAfter(op); + + assert(astCtx && "AST context is not available during lowering prepare"); + auto loweredValue = cxxABI->lowerDynamicCast(builder, *astCtx, op); + + op.replaceAllUsesWith(loweredValue); + op.erase(); +} + static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder, clang::ASTContext *astCtx, mlir::Operation *op, mlir::Type eltTy, @@ -954,6 +987,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) { lowerComplexMulOp(complexMul); else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op)) lowerGlobalOp(glob); + else if (auto dynamicCast = mlir::dyn_cast<cir::DynamicCastOp>(op)) + lowerDynamicCastOp(dynamicCast); else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op)) lowerUnaryOp(unary); } @@ -967,8 +1002,8 @@ void LoweringPreparePass::runOnOperation() { op->walk([&](mlir::Operation *op) { if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp, - cir::ComplexMulOp, cir::ComplexDivOp, cir::GlobalOp, - cir::UnaryOp>(op)) + cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp, + cir::GlobalOp, cir::UnaryOp>(op)) opsToTransform.push_back(op); }); diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepareCXXABI.h b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareCXXABI.h new file mode 100644 index 0000000..2582c332 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareCXXABI.h @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides the LoweringPrepareCXXABI class, which is the base class +// for ABI specific functionalities that are required during LLVM lowering +// prepare. +// +//===----------------------------------------------------------------------===// + +#ifndef CIR_DIALECT_TRANSFORMS__LOWERINGPREPARECXXABI_H +#define CIR_DIALECT_TRANSFORMS__LOWERINGPREPARECXXABI_H + +#include "mlir/IR/Value.h" +#include "clang/AST/ASTContext.h" +#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +namespace cir { + +class LoweringPrepareCXXABI { +public: + static LoweringPrepareCXXABI *createItaniumABI(); + + virtual ~LoweringPrepareCXXABI() {} + + virtual mlir::Value lowerDynamicCast(CIRBaseBuilderTy &builder, + clang::ASTContext &astCtx, + cir::DynamicCastOp op) = 0; +}; + +} // namespace cir + +#endif // CIR_DIALECT_TRANSFORMS__LOWERINGPREPARECXXABI_H diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepareItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareItaniumCXXABI.cpp new file mode 100644 index 0000000..7d3c711 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepareItaniumCXXABI.cpp @@ -0,0 +1,126 @@ +//===--------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with +// LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===--------------------------------------------------------------------===// +// +// This file provides Itanium C++ ABI specific code +// that is used during LLVMIR lowering prepare. +// +//===--------------------------------------------------------------------===// + +#include "LoweringPrepareCXXABI.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Value.h" +#include "mlir/IR/ValueRange.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDataLayout.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/MissingFeatures.h" + +class LoweringPrepareItaniumCXXABI : public cir::LoweringPrepareCXXABI { +public: + mlir::Value lowerDynamicCast(cir::CIRBaseBuilderTy &builder, + clang::ASTContext &astCtx, + cir::DynamicCastOp op) override; +}; + +cir::LoweringPrepareCXXABI *cir::LoweringPrepareCXXABI::createItaniumABI() { + return new LoweringPrepareItaniumCXXABI(); +} + +static void buildBadCastCall(cir::CIRBaseBuilderTy &builder, mlir::Location loc, + mlir::FlatSymbolRefAttr badCastFuncRef) { + builder.createCallOp(loc, badCastFuncRef, cir::VoidType(), + mlir::ValueRange{}); + // TODO(cir): Set the 'noreturn' attribute on the function. + assert(!cir::MissingFeatures::opFuncNoReturn()); + cir::UnreachableOp::create(builder, loc); + builder.clearInsertionPoint(); +} + +static mlir::Value +buildDynamicCastAfterNullCheck(cir::CIRBaseBuilderTy &builder, + cir::DynamicCastOp op) { + mlir::Location loc = op->getLoc(); + mlir::Value srcValue = op.getSrc(); + cir::DynamicCastInfoAttr castInfo = op.getInfo().value(); + + // TODO(cir): consider address space + assert(!cir::MissingFeatures::addressSpace()); + + mlir::Value srcPtr = builder.createBitcast(srcValue, builder.getVoidPtrTy()); + cir::ConstantOp srcRtti = builder.getConstant(loc, castInfo.getSrcRtti()); + cir::ConstantOp destRtti = builder.getConstant(loc, castInfo.getDestRtti()); + cir::ConstantOp offsetHint = + builder.getConstant(loc, castInfo.getOffsetHint()); + + mlir::FlatSymbolRefAttr dynCastFuncRef = castInfo.getRuntimeFunc(); + mlir::Value dynCastFuncArgs[4] = {srcPtr, srcRtti, destRtti, offsetHint}; + + mlir::Value castedPtr = + builder + .createCallOp(loc, dynCastFuncRef, builder.getVoidPtrTy(), + dynCastFuncArgs) + .getResult(); + + assert(mlir::isa<cir::PointerType>(castedPtr.getType()) && + "the return value of __dynamic_cast should be a ptr"); + + /// C++ [expr.dynamic.cast]p9: + /// A failed cast to reference type throws std::bad_cast + if (op.isRefCast()) { + // Emit a cir.if that checks the casted value. + mlir::Value castedValueIsNull = builder.createPtrIsNull(castedPtr); + builder.create<cir::IfOp>( + loc, castedValueIsNull, false, [&](mlir::OpBuilder &, mlir::Location) { + buildBadCastCall(builder, loc, castInfo.getBadCastFunc()); + }); + } + + // Note that castedPtr is a void*. Cast it to a pointer to the destination + // type before return. + return builder.createBitcast(castedPtr, op.getType()); +} + +static mlir::Value +buildDynamicCastToVoidAfterNullCheck(cir::CIRBaseBuilderTy &builder, + clang::ASTContext &astCtx, + cir::DynamicCastOp op) { + llvm_unreachable("dynamic cast to void is NYI"); +} + +mlir::Value +LoweringPrepareItaniumCXXABI::lowerDynamicCast(cir::CIRBaseBuilderTy &builder, + clang::ASTContext &astCtx, + cir::DynamicCastOp op) { + mlir::Location loc = op->getLoc(); + mlir::Value srcValue = op.getSrc(); + + assert(!cir::MissingFeatures::emitTypeCheck()); + + if (op.isRefCast()) + return buildDynamicCastAfterNullCheck(builder, op); + + mlir::Value srcValueIsNotNull = builder.createPtrToBoolCast(srcValue); + return builder + .create<cir::TernaryOp>( + loc, srcValueIsNotNull, + [&](mlir::OpBuilder &, mlir::Location) { + mlir::Value castedValue = + op.isCastToVoid() + ? buildDynamicCastToVoidAfterNullCheck(builder, astCtx, op) + : buildDynamicCastAfterNullCheck(builder, op); + builder.createYield(loc, castedValue); + }, + [&](mlir::OpBuilder &, mlir::Location) { + builder.createYield( + loc, builder.getNullPtr(op.getType(), loc).getResult()); + }) + .getResult(); +} diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index a1ecfc7..26e0ba9 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -1739,7 +1739,6 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite( const mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage()); const StringRef symbol = op.getSymName(); SmallVector<mlir::NamedAttribute> attributes; - mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter); if (init.has_value()) { if (mlir::isa<cir::FPAttr, cir::IntAttr, cir::BoolAttr>(init.value())) { @@ -1771,6 +1770,7 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite( } // Rewrite op. + mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter); auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>( op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()), alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr, attributes); diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 85a13357..40ea513 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -6836,6 +6836,8 @@ const ToolChain &Driver::getToolChain(const ArgList &Args, TC = std::make_unique<toolchains::VEToolChain>(*this, Target, Args); else if (Target.isOHOSFamily()) TC = std::make_unique<toolchains::OHOS>(*this, Target, Args); + else if (Target.isWALI()) + TC = std::make_unique<toolchains::WebAssembly>(*this, Target, Args); else TC = std::make_unique<toolchains::Linux>(*this, Target, Args); break; diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 9abaf79..e9ca8ce 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -29,7 +29,7 @@ #include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h" #include "clang/Analysis/Analyses/CalledOnceCheck.h" #include "clang/Analysis/Analyses/Consumed.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" +#include "clang/Analysis/Analyses/LifetimeSafety.h" #include "clang/Analysis/Analyses/ReachableCode.h" #include "clang/Analysis/Analyses/ThreadSafety.h" #include "clang/Analysis/Analyses/UninitializedValues.h" diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 0ebf56e..51e0ee1 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -111,7 +111,6 @@ add_clang_library(clangSema clangAPINotes clangAST clangAnalysis - clangAnalysisLifetimeSafety clangBasic clangEdit clangLex diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 8aebf53..e8a7ad3 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -10,7 +10,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" #include "clang/AST/Type.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" +#include "clang/Analysis/Analyses/LifetimeAnnotations.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Sema.h" diff --git a/clang/lib/Sema/SemaAMDGPU.cpp b/clang/lib/Sema/SemaAMDGPU.cpp index 45fe80d..e32f437 100644 --- a/clang/lib/Sema/SemaAMDGPU.cpp +++ b/clang/lib/Sema/SemaAMDGPU.cpp @@ -11,9 +11,9 @@ //===----------------------------------------------------------------------===// #include "clang/Sema/SemaAMDGPU.h" +#include "clang/Basic/DiagnosticFrontend.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/TargetBuiltins.h" -#include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Sema/Ownership.h" #include "clang/Sema/Sema.h" #include "llvm/Support/AMDGPUAddrSpace.h" diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 0d8d0fa..35cdfbf 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -17,7 +17,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/TypeLoc.h" -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" +#include "clang/Analysis/Analyses/LifetimeAnnotations.h" #include "clang/Basic/SourceLocation.h" #include "clang/Lex/Lexer.h" #include "clang/Sema/SemaObjC.h" diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 4230ea7..01abc1f 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -166,6 +166,11 @@ static void diagnoseUseOfInternalDeclInInlineFunction(Sema &S, // This is disabled under C++; there are too many ways for this to fire in // contexts where the warning is a false positive, or where it is technically // correct but benign. + // + // WG14 N3622 which removed the constraint entirely in C2y. It is left + // enabled in earlier language modes because this is a constraint in those + // language modes. But in C2y mode, we still want to issue the "incompatible + // with previous standards" diagnostic, too. if (S.getLangOpts().CPlusPlus) return; @@ -190,16 +195,17 @@ static void diagnoseUseOfInternalDeclInInlineFunction(Sema &S, // This last can give us false negatives, but it's better than warning on // wrappers for simple C library functions. const FunctionDecl *UsedFn = dyn_cast<FunctionDecl>(D); - bool DowngradeWarning = S.getSourceManager().isInMainFile(Loc); - if (!DowngradeWarning && UsedFn) - DowngradeWarning = UsedFn->isInlined() || UsedFn->hasAttr<ConstAttr>(); - - S.Diag(Loc, DowngradeWarning ? diag::ext_internal_in_extern_inline_quiet - : diag::ext_internal_in_extern_inline) - << /*IsVar=*/!UsedFn << D; + unsigned DiagID; + if (S.getLangOpts().C2y) + DiagID = diag::warn_c2y_compat_internal_in_extern_inline; + else if ((UsedFn && (UsedFn->isInlined() || UsedFn->hasAttr<ConstAttr>())) || + S.getSourceManager().isInMainFile(Loc)) + DiagID = diag::ext_internal_in_extern_inline_quiet; + else + DiagID = diag::ext_internal_in_extern_inline; + S.Diag(Loc, DiagID) << /*IsVar=*/!UsedFn << D; S.MaybeSuggestAddingStaticToDecl(Current); - S.Diag(D->getCanonicalDecl()->getLocation(), diag::note_entity_declared_at) << D; } diff --git a/clang/lib/Sema/SemaOpenACC.cpp b/clang/lib/Sema/SemaOpenACC.cpp index 4824b5a..f3969a9 100644 --- a/clang/lib/Sema/SemaOpenACC.cpp +++ b/clang/lib/Sema/SemaOpenACC.cpp @@ -2759,7 +2759,7 @@ OpenACCPrivateRecipe SemaOpenACC::CreatePrivateInitRecipe(const Expr *VarExpr) { // Array sections are special, and we have to treat them that way. if (const auto *ASE = dyn_cast<ArraySectionExpr>(VarExpr->IgnoreParenImpCasts())) - VarTy = ArraySectionExpr::getBaseOriginalType(ASE); + VarTy = ASE->getElementType(); VarDecl *AllocaDecl = CreateAllocaDecl( getASTContext(), SemaRef.getCurContext(), VarExpr->getBeginLoc(), @@ -2795,7 +2795,7 @@ SemaOpenACC::CreateFirstPrivateInitRecipe(const Expr *VarExpr) { // Array sections are special, and we have to treat them that way. if (const auto *ASE = dyn_cast<ArraySectionExpr>(VarExpr->IgnoreParenImpCasts())) - VarTy = ArraySectionExpr::getBaseOriginalType(ASE); + VarTy = ASE->getElementType(); VarDecl *AllocaDecl = CreateAllocaDecl( getASTContext(), SemaRef.getCurContext(), VarExpr->getBeginLoc(), @@ -2896,7 +2896,7 @@ OpenACCReductionRecipe SemaOpenACC::CreateReductionInitRecipe( // Array sections are special, and we have to treat them that way. if (const auto *ASE = dyn_cast<ArraySectionExpr>(VarExpr->IgnoreParenImpCasts())) - VarTy = ArraySectionExpr::getBaseOriginalType(ASE); + VarTy = ASE->getElementType(); VarDecl *AllocaDecl = CreateAllocaDecl( getASTContext(), SemaRef.getCurContext(), VarExpr->getBeginLoc(), diff --git a/clang/lib/Sema/SemaOpenACCClause.cpp b/clang/lib/Sema/SemaOpenACCClause.cpp index b086929..881e960 100644 --- a/clang/lib/Sema/SemaOpenACCClause.cpp +++ b/clang/lib/Sema/SemaOpenACCClause.cpp @@ -1915,51 +1915,34 @@ SemaOpenACC::ActOnClause(ArrayRef<const OpenACCClause *> ExistingClauses, return Result; } -/// OpenACC 3.3 section 2.5.15: -/// At a mininmum, the supported data types include ... the numerical data types -/// in C, C++, and Fortran. -/// -/// If the reduction var is a composite variable, each -/// member of the composite variable must be a supported datatype for the -/// reduction operation. -ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind, - OpenACCReductionOperator ReductionOp, - Expr *VarExpr) { - // For now, we only support 'scalar' types, or composites/arrays of scalar - // types. - VarExpr = VarExpr->IgnoreParenCasts(); +bool SemaOpenACC::CheckReductionVarType(Expr *VarExpr) { SourceLocation VarLoc = VarExpr->getBeginLoc(); SmallVector<PartialDiagnosticAt> Notes; - QualType CurType = VarExpr->getType(); - - // For array like things, the expression can either be an array element - // (subscript expr), array section, or array type. Peel those off, and add - // notes in case we find an illegal kind. We'll allow scalar or composite of - // scalars inside of this. - if (auto *ASE = dyn_cast<ArraySectionExpr>(VarExpr)) { - QualType BaseType = ArraySectionExpr::getBaseOriginalType(ASE); + // The standard isn't clear how many levels of 'array element' or 'subarray' + // are permitted, but we can handle as many as we need, so we'll strip them + // off here. This will result in CurType being the actual 'type' of the + // expression, which is what we are looking to check. + QualType CurType = isa<ArraySectionExpr>(VarExpr) + ? ArraySectionExpr::getBaseOriginalType(VarExpr) + : VarExpr->getType(); + + // This can happen when we have a dependent type in an array element that the + // above function has tried to 'unwrap'. Since this can only happen with + // dependence, just let it go. + if (CurType.isNull()) + return false; - PartialDiagnostic PD = PDiag(diag::note_acc_reduction_array) - << diag::OACCReductionArray::Section << BaseType; - Notes.push_back({ASE->getBeginLoc(), PD}); - - CurType = getASTContext().getBaseElementType(BaseType); - } else if (auto *SubExpr = dyn_cast<ArraySubscriptExpr>(VarExpr)) { - // Array subscript already results in the type of the thing as its type, so - // there is no type to change here. - PartialDiagnostic PD = - PDiag(diag::note_acc_reduction_array) - << diag::OACCReductionArray::Subscript - << SubExpr->getBase()->IgnoreParenImpCasts()->getType(); - Notes.push_back({SubExpr->getBeginLoc(), PD}); - } else if (auto *AT = getASTContext().getAsArrayType(CurType)) { + // If we are still an array type, we allow 1 level of 'unpeeling' of the + // array. The standard isn't clear here whether this is allowed, but + // array-of-valid-things makes sense. + if (auto *AT = getASTContext().getAsArrayType(CurType)) { // If we're already the array type, peel off the array and leave the element // type. - CurType = getASTContext().getBaseElementType(AT); PartialDiagnostic PD = PDiag(diag::note_acc_reduction_array) << diag::OACCReductionArray::ArrayTy << CurType; Notes.push_back({VarLoc, PD}); + CurType = AT->getElementType(); } auto IsValidMemberOfComposite = [](QualType Ty) { @@ -1974,31 +1957,26 @@ ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind, for (auto [Loc, PD] : Notes) Diag(Loc, PD); - Diag(VarLoc, diag::note_acc_reduction_type_summary); + return Diag(VarLoc, diag::note_acc_reduction_type_summary); }; // If the type is already scalar, or is dependent, just give up. if (IsValidMemberOfComposite(CurType)) { // Nothing to do here, is valid. } else if (auto *RD = CurType->getAsRecordDecl()) { - if (!RD->isStruct() && !RD->isClass()) { - EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) - << RD << diag::OACCReductionTy::NotClassStruct); - return ExprError(); - } + if (!RD->isStruct() && !RD->isClass()) + return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) + << RD + << diag::OACCReductionTy::NotClassStruct); - if (!RD->isCompleteDefinition()) { - EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) - << RD << diag::OACCReductionTy::NotComplete); - return ExprError(); - } + if (!RD->isCompleteDefinition()) + return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) + << RD << diag::OACCReductionTy::NotComplete); if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD); - CXXRD && !CXXRD->isAggregate()) { - EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) - << CXXRD << diag::OACCReductionTy::NotAgg); - return ExprError(); - } + CXXRD && !CXXRD->isAggregate()) + return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) + << CXXRD << diag::OACCReductionTy::NotAgg); for (FieldDecl *FD : RD->fields()) { if (!IsValidMemberOfComposite(FD->getType())) { @@ -2007,17 +1985,37 @@ ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind, << FD->getName() << RD->getName(); Notes.push_back({FD->getBeginLoc(), PD}); // TODO: member here.note_acc_reduction_member_of_composite - EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) - << FD->getType() - << diag::OACCReductionTy::MemberNotScalar); - return ExprError(); + return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) + << FD->getType() + << diag::OACCReductionTy::MemberNotScalar); } } } else { - EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) - << CurType << diag::OACCReductionTy::NotScalar); + return EmitDiags(VarLoc, PDiag(diag::err_acc_reduction_type) + << CurType + << diag::OACCReductionTy::NotScalar); } + return false; +} + +/// OpenACC 3.3 section 2.5.15: +/// At a mininmum, the supported data types include ... the numerical data types +/// in C, C++, and Fortran. +/// +/// If the reduction var is a composite variable, each +/// member of the composite variable must be a supported datatype for the +/// reduction operation. +ExprResult SemaOpenACC::CheckReductionVar(OpenACCDirectiveKind DirectiveKind, + OpenACCReductionOperator ReductionOp, + Expr *VarExpr) { + // For now, we only support 'scalar' types, or composites/arrays of scalar + // types. + VarExpr = VarExpr->IgnoreParenCasts(); + + if (CheckReductionVarType(VarExpr)) + return ExprError(); + // OpenACC3.3: 2.9.11: Reduction clauses on nested constructs for the same // reduction 'var' must have the same reduction operator. if (!VarExpr->isInstantiationDependent()) { diff --git a/clang/test/C/C2y/n3622.c b/clang/test/C/C2y/n3622.c new file mode 100644 index 0000000..95b92e8 --- /dev/null +++ b/clang/test/C/C2y/n3622.c @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -verify=good -pedantic -Wall -std=c2y %s +// RUN: %clang_cc1 -verify=compat,expected -pedantic -Wall -Wpre-c2y-compat -std=c2y %s +// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -std=c23 %s +// RUN: %clang_cc1 -verify=ped,expected -pedantic -Wall -std=c17 %s +// good-no-diagnostics + +/* WG14 N3622: Clang 22 + * Allow calling static inline within extern inline + * + * This verifies that a constraint from previous standards is no longer + * triggered in C2y mode. The constraint is with calling a static function + * or using a static variable from an inline function with external linkage. + */ + +static void static_func(void) {} // expected-note {{declared here}} +static int static_var; // expected-note {{declared here}} + +extern inline void test(void) { + static_func(); /* ped-warning {{using static function 'static_func' in an inline function with external linkage is a C2y extension}} + compat-warning {{using static function 'static_func' in an inline function with external linkage is incompatible with standards before C2y}} + */ + static_var = 12; /* ped-warning {{using static variable 'static_var' in an inline function with external linkage is a C2y extension}} + compat-warning {{using static variable 'static_var' in an inline function with external linkage is incompatible with standards before C2y}} + */ +} diff --git a/clang/test/C/C2y/n3623.c b/clang/test/C/C2y/n3623.c index a557eda..5e004c1 100644 --- a/clang/test/C/C2y/n3623.c +++ b/clang/test/C/C2y/n3623.c @@ -64,7 +64,10 @@ int wmain(int, wchar_t *[], wchar_t *[]) {} // Invalid signatures. #if defined(INVALID1) inline int main(int, char *[]); /* invalid-error {{'main' is not allowed to be declared inline}} */ +#if !__is_target_os(darwin) +// This test doesn't make sense on Darwin where four arguments are allowed. int main(int, char *[], char *[], float); /* invalid-error {{too many parameters (4) for 'main': must be 0, 2, or 3}} */ +#endif float main(int); /* invalid-error {{'main' must return 'int'}} */ _Noreturn int main(int, char *[]); /* invalid-warning {{'main' is not allowed to be declared _Noreturn}} invalid-note {{remove '_Noreturn'}} diff --git a/clang/test/CIR/CodeGen/dynamic-cast.cpp b/clang/test/CIR/CodeGen/dynamic-cast.cpp index e5244b2..b493840 100644 --- a/clang/test/CIR/CodeGen/dynamic-cast.cpp +++ b/clang/test/CIR/CodeGen/dynamic-cast.cpp @@ -1,5 +1,11 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t.before.log // RUN: FileCheck %s --input-file=%t.before.log -check-prefix=CIR-BEFORE +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir -mmlir --mlir-print-ir-after=cir-lowering-prepare %s -o %t.cir 2> %t.after.log +// RUN: FileCheck %s --input-file=%t.after.log -check-prefix=CIR-AFTER +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck %s --input-file=%t-cir.ll -check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o %t.ll +// RUN: FileCheck %s --input-file=%t.ll -check-prefix=OGCG struct Base { virtual ~Base(); @@ -19,6 +25,46 @@ Derived *ptr_cast(Base *b) { // CIR-BEFORE: %{{.+}} = cir.dyn_cast ptr %{{.+}} : !cir.ptr<!rec_Base> -> !cir.ptr<!rec_Derived> #dyn_cast_info__ZTI4Base__ZTI7Derived // CIR-BEFORE: } +// CIR-AFTER: cir.func dso_local @_Z8ptr_castP4Base +// CIR-AFTER: %[[SRC:.*]] = cir.load{{.*}} %{{.+}} : !cir.ptr<!cir.ptr<!rec_Base>>, !cir.ptr<!rec_Base> +// CIR-AFTER-NEXT: %[[SRC_IS_NOT_NULL:.*]] = cir.cast ptr_to_bool %[[SRC]] : !cir.ptr<!rec_Base> -> !cir.bool +// CIR-AFTER-NEXT: %{{.+}} = cir.ternary(%[[SRC_IS_NOT_NULL]], true { +// CIR-AFTER-NEXT: %[[SRC_VOID_PTR:.*]] = cir.cast bitcast %[[SRC]] : !cir.ptr<!rec_Base> -> !cir.ptr<!void> +// CIR-AFTER-NEXT: %[[BASE_RTTI:.*]] = cir.const #cir.global_view<@_ZTI4Base> : !cir.ptr<!u8i> +// CIR-AFTER-NEXT: %[[DERIVED_RTTI:.*]] = cir.const #cir.global_view<@_ZTI7Derived> : !cir.ptr<!u8i> +// CIR-AFTER-NEXT: %[[HINT:.*]] = cir.const #cir.int<0> : !s64i +// CIR-AFTER-NEXT: %[[RT_CALL_RET:.*]] = cir.call @__dynamic_cast(%[[SRC_VOID_PTR]], %[[BASE_RTTI]], %[[DERIVED_RTTI]], %[[HINT]]) : (!cir.ptr<!void>, !cir.ptr<!u8i>, !cir.ptr<!u8i>, !s64i) -> !cir.ptr<!void> +// CIR-AFTER-NEXT: %[[CASTED:.*]] = cir.cast bitcast %[[RT_CALL_RET]] : !cir.ptr<!void> -> !cir.ptr<!rec_Derived> +// CIR-AFTER-NEXT: cir.yield %[[CASTED]] : !cir.ptr<!rec_Derived> +// CIR-AFTER-NEXT: }, false { +// CIR-AFTER-NEXT: %[[NULL_PTR:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_Derived> +// CIR-AFTER-NEXT: cir.yield %[[NULL_PTR]] : !cir.ptr<!rec_Derived> +// CIR-AFTER-NEXT: }) : (!cir.bool) -> !cir.ptr<!rec_Derived> +// CIR-AFTER: } + +// LLVM: define {{.*}} @_Z8ptr_castP4Base +// LLVM: %[[IS_NOT_NULL:.*]] = icmp ne ptr %[[PTR:.*]], null +// LLVM: br i1 %[[IS_NOT_NULL]], label %[[NOT_NULL:.*]], label %[[NULL:.*]] +// LLVM: [[NOT_NULL]]: +// LLVM: %[[RESULT:.*]] = call ptr @__dynamic_cast(ptr %[[PTR]], ptr @_ZTI4Base, ptr @_ZTI7Derived, i64 0) +// LLVM: br label %[[DONE:.*]] +// LLVM: [[NULL]]: +// LLVM: br label %[[DONE]] +// LLVM: [[DONE]]: +// LLVM: %[[RET:.*]] = phi ptr [ null, %[[NULL]] ], [ %[[RESULT]], %[[NOT_NULL]] ] + +// OGCG: define {{.*}} @_Z8ptr_castP4Base +// OGCG: %[[IS_NULL:.*]] = icmp eq ptr %[[PTR:.*]], null +// OGCG: br i1 %[[IS_NULL]], label %[[NULL:.*]], label %[[NOT_NULL:.*]] +// OGCG: [[NOT_NULL]]: +// OGCG: %[[RESULT:.*]] = call ptr @__dynamic_cast(ptr %[[PTR]], ptr @_ZTI4Base, ptr @_ZTI7Derived, i64 0) +// OGCG: br label %[[DONE:.*]] +// OGCG: [[NULL]]: +// OGCG: br label %[[DONE]] +// OGCG: [[DONE]]: +// OGCG: %[[RET:.*]] = phi ptr [ %[[RESULT]], %[[NOT_NULL]] ], [ null, %[[NULL]] ] + + Derived &ref_cast(Base &b) { return dynamic_cast<Derived &>(b); } @@ -26,3 +72,32 @@ Derived &ref_cast(Base &b) { // CIR-BEFORE: cir.func dso_local @_Z8ref_castR4Base // CIR-BEFORE: %{{.+}} = cir.dyn_cast ref %{{.+}} : !cir.ptr<!rec_Base> -> !cir.ptr<!rec_Derived> #dyn_cast_info__ZTI4Base__ZTI7Derived // CIR-BEFORE: } + +// CIR-AFTER: cir.func dso_local @_Z8ref_castR4Base +// CIR-AFTER: %[[SRC_VOID_PTR:.*]] = cir.cast bitcast %{{.+}} : !cir.ptr<!rec_Base> -> !cir.ptr<!void> +// CIR-AFTER-NEXT: %[[SRC_RTTI:.*]] = cir.const #cir.global_view<@_ZTI4Base> : !cir.ptr<!u8i> +// CIR-AFTER-NEXT: %[[DEST_RTTI:.*]] = cir.const #cir.global_view<@_ZTI7Derived> : !cir.ptr<!u8i> +// CIR-AFTER-NEXT: %[[OFFSET_HINT:.*]] = cir.const #cir.int<0> : !s64i +// CIR-AFTER-NEXT: %[[CASTED_PTR:.*]] = cir.call @__dynamic_cast(%[[SRC_VOID_PTR]], %[[SRC_RTTI]], %[[DEST_RTTI]], %[[OFFSET_HINT]]) : (!cir.ptr<!void>, !cir.ptr<!u8i>, !cir.ptr<!u8i>, !s64i) -> !cir.ptr<!void> +// CIR-AFTER-NEXT: %[[NULL_PTR:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> +// CIR-AFTER-NEXT: %[[CASTED_PTR_IS_NULL:.*]] = cir.cmp(eq, %[[CASTED_PTR]], %[[NULL_PTR]]) : !cir.ptr<!void>, !cir.bool +// CIR-AFTER-NEXT: cir.if %[[CASTED_PTR_IS_NULL]] { +// CIR-AFTER-NEXT: cir.call @__cxa_bad_cast() : () -> () +// CIR-AFTER-NEXT: cir.unreachable +// CIR-AFTER-NEXT: } +// CIR-AFTER-NEXT: %{{.+}} = cir.cast bitcast %[[CASTED_PTR]] : !cir.ptr<!void> -> !cir.ptr<!rec_Derived> +// CIR-AFTER: } + +// LLVM: define {{.*}} ptr @_Z8ref_castR4Base +// LLVM: %[[RESULT:.*]] = call ptr @__dynamic_cast(ptr %{{.*}}, ptr @_ZTI4Base, ptr @_ZTI7Derived, i64 0) +// LLVM: %[[IS_NULL:.*]] = icmp eq ptr %[[RESULT]], null +// LLVM: br i1 %[[IS_NULL]], label %[[BAD_CAST:.*]], label %[[DONE:.*]] +// LLVM: [[BAD_CAST]]: +// LLVM: call void @__cxa_bad_cast() + +// OGCG: define {{.*}} ptr @_Z8ref_castR4Base +// OGCG: %[[RESULT:.*]] = call ptr @__dynamic_cast(ptr %0, ptr @_ZTI4Base, ptr @_ZTI7Derived, i64 0) +// OGCG: %[[IS_NULL:.*]] = icmp eq ptr %[[RESULT]], null +// OGCG: br i1 %[[IS_NULL]], label %[[BAD_CAST:.*]], label %[[DONE:.*]] +// OGCG: [[BAD_CAST]]: +// OGCG: call void @__cxa_bad_cast() diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp new file mode 100644 index 0000000..8f0b3c4 --- /dev/null +++ b/clang/test/CIR/CodeGen/try-catch.cpp @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG + +void empty_try_block_with_catch_all() { + try {} catch (...) {} +} + +// CIR: cir.func{{.*}} @_Z30empty_try_block_with_catch_allv() +// CIR: cir.return + +// LLVM: define{{.*}} void @_Z30empty_try_block_with_catch_allv() +// LLVM: ret void + +// OGCG: define{{.*}} void @_Z30empty_try_block_with_catch_allv() +// OGCG: ret void + +void empty_try_block_with_catch_with_int_exception() { + try {} catch (int e) {} +} + +// CIR: cir.func{{.*}} @_Z45empty_try_block_with_catch_with_int_exceptionv() +// CIR: cir.return + +// LLVM: define{{.*}} void @_Z45empty_try_block_with_catch_with_int_exceptionv() +// LLVM: ret void + +// OGCG: define{{.*}} void @_Z45empty_try_block_with_catch_with_int_exceptionv() +// OGCG: ret void diff --git a/clang/test/DebugInfo/KeyInstructions/flag.cpp b/clang/test/DebugInfo/KeyInstructions/flag.cpp index a5cd855..6aeeed6 100644 --- a/clang/test/DebugInfo/KeyInstructions/flag.cpp +++ b/clang/test/DebugInfo/KeyInstructions/flag.cpp @@ -1,12 +1,15 @@ // RUN: %clang -### -target x86_64 -c -gdwarf -gkey-instructions %s 2>&1 | FileCheck %s --check-prefixes=KEY-INSTRUCTIONS // RUN: %clang -### -target x86_64 -c -gdwarf -gno-key-instructions %s 2>&1 | FileCheck %s --check-prefixes=NO-KEY-INSTRUCTIONS +// RUN: %clang -### -target x86_64 -c -gno-key-instructions %s 2>&1 | FileCheck %s --check-prefixes=NO-DEBUG //// Help. // RUN %clang --help | FileCheck %s --check-prefix=HELP -// HELP: -gkey-instructions Enable Key Instructions, which reduces the jumpiness of debug stepping in optimized C/C++ code in some debuggers. DWARF only. Implies -g. +// HELP: -gkey-instructions Enable Key Instructions, which reduces the jumpiness of debug stepping in optimized C/C++ code in some debuggers. DWARF only. // KEY-INSTRUCTIONS: "-gkey-instructions" // NO-KEY-INSTRUCTIONS-NOT: key-instructions +// NO-DEBUG-NOT: debug-info-kind +// NO-DEBUG-NOT: dwarf //// Help hidden: flag should not be visible. // RUN: %clang --help | FileCheck %s --check-prefix=HELP diff --git a/clang/test/Driver/debug-options.c b/clang/test/Driver/debug-options.c index 73f2f40..45ac450 100644 --- a/clang/test/Driver/debug-options.c +++ b/clang/test/Driver/debug-options.c @@ -268,11 +268,11 @@ // RUN: %clang -### -c %s 2>&1 | FileCheck -check-prefix=NORNGBSE %s // RUN: %clang -### -c -fdebug-ranges-base-address -fno-debug-ranges-base-address %s 2>&1 | FileCheck -check-prefix=NORNGBSE %s // -// RUN: %clang -### -c -gomit-unreferenced-methods -fno-standalone-debug %s 2>&1 | FileCheck -check-prefix=INCTYPES %s +// RUN: %clang -### -c -g -gomit-unreferenced-methods -fno-standalone-debug %s 2>&1 | FileCheck -check-prefix=INCTYPES %s // RUN: %clang -### -c %s 2>&1 | FileCheck -check-prefix=NOINCTYPES %s -// RUN: %clang -### -c -gomit-unreferenced-methods -fdebug-types-section -target x86_64-unknown-linux %s 2>&1 \ +// RUN: %clang -### -c -g -gomit-unreferenced-methods -fdebug-types-section -target x86_64-unknown-linux %s 2>&1 \ // RUN: | FileCheck -check-prefix=NOINCTYPES %s -// RUN: %clang -### -c -gomit-unreferenced-methods -fstandalone-debug %s 2>&1 | FileCheck -check-prefix=NOINCTYPES %s +// RUN: %clang -### -c -g -gomit-unreferenced-methods -fstandalone-debug %s 2>&1 | FileCheck -check-prefix=NOINCTYPES %s // // RUN: %clang -### -c -glldb %s 2>&1 | FileCheck -check-prefix=NOPUB %s // RUN: %clang -### -c -glldb -gno-pubnames %s 2>&1 | FileCheck -check-prefix=NOPUB %s diff --git a/clang/test/Driver/wasm-toolchain.c b/clang/test/Driver/wasm-toolchain.c index 91803fe..29a94ae 100644 --- a/clang/test/Driver/wasm-toolchain.c +++ b/clang/test/Driver/wasm-toolchain.c @@ -296,3 +296,10 @@ // RUN: | FileCheck -check-prefix=LINK_WASIP2_USE_WASMLD %s // LINK_WASIP2_USE_WASMLD: "-cc1" {{.*}} "-o" "[[temp:[^"]*]]" // LINK_WASIP2_USE_WASMLD: wasm-ld{{.*}}" "-m" "wasm32" {{.*}} "[[temp]]" {{.*}} + +// Basic `wasm32-linux-muslwali` compile-link test. + +// RUN: %clang -### --target=wasm32-linux-muslwali --sysroot=/foo %s 2>&1 \ +// RUN: | FileCheck -check-prefix=LINK_WALI_BASIC %s +// LINK_WALI_BASIC: "-cc1" {{.*}} "-o" "[[temp:[^"]*]]" +// LINK_WALI_BASIC: wasm-ld{{.*}}" "-L/foo/lib/wasm32-linux-muslwali" "crt1.o" "[[temp]]" "-lc" "{{.*[/\\]}}libclang_rt.builtins.a" "-o" "a.out" diff --git a/clang/test/Driver/wasm-toolchain.cpp b/clang/test/Driver/wasm-toolchain.cpp index ba1c55b..d7ff76c 100644 --- a/clang/test/Driver/wasm-toolchain.cpp +++ b/clang/test/Driver/wasm-toolchain.cpp @@ -86,3 +86,28 @@ // COMPILE_STDCXX: "-internal-isystem" "[[RESOURCE_DIR]]{{(/|\\\\)}}include" // COMPILE_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi" // COMPILE_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include" + +// RUN: %clangxx -### --target=wasm32-linux-muslwali --stdlib=libc++ %s 2>&1 \ +// RUN: --sysroot=%S/Inputs/basic_linux_libcxx_tree/usr \ +// RUN: | FileCheck -check-prefix=COMPILE_WALI %s +// COMPILE_WALI: "-cc1" +// COMPILE_WALI: "-resource-dir" "[[RESOURCE_DIR:[^"]*]]" +// COMPILE_WALI: "-isysroot" "[[SYSROOT:[^"]+]]" +// COMPILE_WALI: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-linux-muslwali/c++/v1" +// COMPILE_WALI: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/c++/v1" +// COMPILE_WALI: "-internal-isystem" "[[RESOURCE_DIR]]{{(/|\\\\)}}include" +// COMPILE_WALI: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-linux-muslwali" +// COMPILE_WALI: "-internal-isystem" "[[SYSROOT:[^"]+]]/include" + +// RUN: %clangxx -### --target=wasm32-linux-muslwali --stdlib=libstdc++ %s 2>&1 \ +// RUN: --sysroot=%S/Inputs/basic_linux_libstdcxx_libcxxv2_tree/usr \ +// RUN: | FileCheck -check-prefix=COMPILE_WALI_STDCXX %s +// COMPILE_WALI_STDCXX: "-cc1" +// COMPILE_WALI_STDCXX: "-resource-dir" "[[RESOURCE_DIR:[^"]*]]" +// COMPILE_WALI_STDCXX: "-isysroot" "[[SYSROOT:[^"]+]]" +// COMPILE_WALI_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/c++/4.8/wasm32-linux-muslwali" +// COMPILE_WALI_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/c++/4.8" +// COMPILE_WALI_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/c++/4.8/backward" +// COMPILE_WALI_STDCXX: "-internal-isystem" "[[RESOURCE_DIR]]{{(/|\\\\)}}include" +// COMPILE_WALI_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-linux-muslwali" +// COMPILE_WALI_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include" diff --git a/clang/test/InstallAPI/project-header-only-args-visibility.test b/clang/test/InstallAPI/project-header-only-args-visibility.test new file mode 100644 index 0000000..0403487 --- /dev/null +++ b/clang/test/InstallAPI/project-header-only-args-visibility.test @@ -0,0 +1,69 @@ +; RUN: rm -rf %t +; RUN: split-file %s %t +; RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json + +; RUN: clang-installapi \ +; RUN: -target arm64-apple-macos26 -install_name @rpath/libfoo.dylib \ +; RUN: -current_version 1 -compatibility_version 1 \ +; RUN: -Xproject -fvisibility=hidden -I%t/usr/include \ +; RUN: -I%t -dynamiclib %t/inputs.json \ +; RUN: -o %t/output.tbd 2>&1 | FileCheck %s --allow-empty +; RUN: llvm-readtapi --compare %t/output.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty + +; CHECK-NOT: error +; CHECK-NOT: warning + +//--- usr/include/public.h +int foo(void); + +//--- project.h +int bar(void); + +//--- expected.tbd +{ + "main_library": { + "exported_symbols": [ + { + "text": { + "global": [ + "_foo" + ] + } + } + ], + "flags": [ + { + "attributes": [ + "not_app_extension_safe" + ] + } + ], + "install_names": [ + { + "name": "@rpath/libfoo.dylib" + } + ], + "target_info": [ + { + "min_deployment": "26", + "target": "arm64-macos" + } + ] + }, + "tapi_tbd_version": 5 +} + +;--- inputs.json.in +{ + "headers": [ + { + "path" : "DSTROOT/usr/include/public.h", + "type" : "public" + }, + { + "path" : "DSTROOT/project.h", + "type" : "project" + } + ], + "version": "3" +} diff --git a/clang/test/Sema/inline.c b/clang/test/Sema/inline.c index 804c510..6361db8 100644 --- a/clang/test/Sema/inline.c +++ b/clang/test/Sema/inline.c @@ -11,14 +11,14 @@ static int staticFunction(void); // expected-note + {{'staticFunction' declared static struct { int x; } staticStruct; // expected-note + {{'staticStruct' declared here}} inline int useStatic (void) { // expected-note 3 {{use 'static' to give inline function 'useStatic' internal linkage}} - staticFunction(); // expected-warning{{static function 'staticFunction' is used in an inline function with external linkage}} - (void)staticStruct.x; // expected-warning{{static variable 'staticStruct' is used in an inline function with external linkage}} - return staticVar; // expected-warning{{static variable 'staticVar' is used in an inline function with external linkage}} + staticFunction(); // expected-warning{{using static function 'staticFunction' in an inline function with external linkage is a C2y extension}} + (void)staticStruct.x; // expected-warning{{using static variable 'staticStruct' in an inline function with external linkage is a C2y extension}} + return staticVar; // expected-warning{{using static variable 'staticVar' in an inline function with external linkage is a C2y extension}} } extern inline int useStaticFromExtern (void) { // no suggestions - staticFunction(); // expected-warning{{static function 'staticFunction' is used in an inline function with external linkage}} - return staticVar; // expected-warning{{static variable 'staticVar' is used in an inline function with external linkage}} + staticFunction(); // expected-warning{{using static function 'staticFunction' in an inline function with external linkage is a C2y extension}} + return staticVar; // expected-warning{{using static variable 'staticVar' in an inline function with external linkage is a C2y extension}} } static inline int useStaticFromStatic (void) { @@ -67,8 +67,8 @@ inline int useStaticMainFile (void) { #pragma clang diagnostic warning "-Wstatic-in-inline" inline int useStaticAgain (void) { // expected-note 2 {{use 'static' to give inline function 'useStaticAgain' internal linkage}} - staticFunction(); // expected-warning{{static function 'staticFunction' is used in an inline function with external linkage}} - return staticVar; // expected-warning{{static variable 'staticVar' is used in an inline function with external linkage}} + staticFunction(); // expected-warning{{using static function 'staticFunction' in an inline function with external linkage is a C2y extension}} + return staticVar; // expected-warning{{using static variable 'staticVar' in an inline function with external linkage is a C2y extension}} } #pragma clang diagnostic pop @@ -86,8 +86,8 @@ extern inline void defineStaticVarInExtern(void) { // Check behavior of line markers. # 1 "XXX.h" 1 inline int useStaticMainFileInLineMarker(void) { // expected-note 2 {{use 'static' to give inline function 'useStaticMainFileInLineMarker' internal linkage}} - staticFunction(); // expected-warning{{static function 'staticFunction' is used in an inline function with external linkage}} - return staticVar; // expected-warning{{static variable 'staticVar' is used in an inline function with external linkage}} + staticFunction(); // expected-warning{{using static function 'staticFunction' in an inline function with external linkage is a C2y extension}} + return staticVar; // expected-warning{{using static variable 'staticVar' in an inline function with external linkage is a C2y extension}} } # 100 "inline.c" 2 diff --git a/clang/test/SemaOpenACC/combined-construct-reduction-clause.cpp b/clang/test/SemaOpenACC/combined-construct-reduction-clause.cpp index 5aa90bd..72d7e6b 100644 --- a/clang/test/SemaOpenACC/combined-construct-reduction-clause.cpp +++ b/clang/test/SemaOpenACC/combined-construct-reduction-clause.cpp @@ -166,19 +166,17 @@ void uses(unsigned Parm) { CompositeHasComposite CoCArr[5]; // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of array type 'CompositeHasComposite'}} + // expected-note@+3{{used as element type of array type 'CompositeHasComposite[5]'}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel loop reduction(+:CoCArr) for(int i = 0; i < 5; ++i); - // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of array type 'CompositeHasComposite[5]'}} + // expected-error@+3{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel loop reduction(+:CoCArr[3]) for(int i = 0; i < 5; ++i); - // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of sub-array type 'CompositeHasComposite'}} + // expected-error@+3{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel loop reduction(+:CoCArr[1:1]) diff --git a/clang/test/SemaOpenACC/compute-construct-reduction-clause.c b/clang/test/SemaOpenACC/compute-construct-reduction-clause.c index 07cb498..265c498 100644 --- a/clang/test/SemaOpenACC/compute-construct-reduction-clause.c +++ b/clang/test/SemaOpenACC/compute-construct-reduction-clause.c @@ -72,8 +72,7 @@ void uses(unsigned Parm) { while (1); struct CompositeHasComposite ChCArray[5]; - // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of sub-array type 'struct CompositeHasComposite'}} + // expected-error@+3{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(&: CoS, Array[I], ChCArray[0:I]) diff --git a/clang/test/SemaOpenACC/compute-construct-reduction-clause.cpp b/clang/test/SemaOpenACC/compute-construct-reduction-clause.cpp index 9c2f3d9..edc67ce 100644 --- a/clang/test/SemaOpenACC/compute-construct-reduction-clause.cpp +++ b/clang/test/SemaOpenACC/compute-construct-reduction-clause.cpp @@ -91,19 +91,17 @@ void uses(unsigned Parm) { CompositeHasComposite CoCArr[5]; // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of array type 'CompositeHasComposite'}} + // expected-note@+3{{used as element type of array type 'CompositeHasComposite[5]'}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(+:CoCArr) while (1); - // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of array type 'CompositeHasComposite[5]'}} + // expected-error@+3{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(+:CoCArr[3]) while (1); - // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of sub-array type 'CompositeHasComposite'}} + // expected-error@+3{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(+:CoCArr[1:1]) @@ -121,7 +119,7 @@ void uses(unsigned Parm) { int *IPtrArr[5]; // expected-error@+3{{invalid type 'int *' used in OpenACC 'reduction' variable reference; type is not a scalar value, or array of scalars, or composite of scalars}} - // expected-note@+2{{used as element type of array type 'int *'}} + // expected-note@+2{{used as element type of array type 'int *[5]'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(+:IPtrArr) while (1); @@ -136,7 +134,7 @@ void uses(unsigned Parm) { HasPtr HPArr[5]; // expected-error@+4{{invalid type 'int *' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of array type 'HasPtr'}} + // expected-note@+3{{used as element type of array type 'HasPtr[5]'}} // expected-note@#HASPTR{{used as field 'I' of composite 'HasPtr'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(+:HPArr) @@ -152,7 +150,7 @@ void uses(unsigned Parm) { #pragma acc parallel reduction(+:CplxI) while (1); // expected-error@+3{{invalid type '_Complex int' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+2{{used as element type of array type '_Complex int'}} + // expected-note@+2{{used as element type of array type '_Complex int[5]'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(+:CplxIArr) while (1); @@ -161,7 +159,7 @@ void uses(unsigned Parm) { #pragma acc parallel reduction(+:CplxF) while (1); // expected-error@+3{{invalid type '_Complex float' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+2{{used as element type of array type '_Complex float'}} + // expected-note@+2{{used as element type of array type '_Complex float[5]'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc parallel reduction(+:CplxFArr) while (1); @@ -242,6 +240,50 @@ void TemplUses(T Parm, U CoS, V ChC) { // expected-error@+1{{OpenACC variable is not a valid variable name, sub-array, array element, or composite variable member}} #pragma acc parallel reduction(&: ChCPtr->COS) while (1); + + T ThreeDArray[3][4][5]; + + // expected-error@+3{{invalid type 'int[4][5]' used in OpenACC 'reduction' variable reference; type is not a scalar value}} + // expected-note@+2{{used as element type of array type 'int[3][4][5]'}} + // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} +#pragma acc parallel reduction(+:ThreeDArray) + while (1); + // expected-error@+3{{invalid type 'int[5]' used in OpenACC 'reduction' variable reference; type is not a scalar value}} + // expected-note@+2{{used as element type of array type 'int[4][5]'}} + // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} +#pragma acc parallel reduction(+:ThreeDArray[1:1]) + while (1); + // expected-error@+3{{invalid type 'int[5]' used in OpenACC 'reduction' variable reference; type is not a scalar value}} + // expected-note@+2{{used as element type of array type 'int[4][5]'}} + // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} +#pragma acc parallel reduction(+:ThreeDArray[1]) + while (1); + +#pragma acc parallel reduction(+:ThreeDArray[1:1][1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1:1][1:1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1][1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1][1:1]) + while (1); + +#pragma acc parallel reduction(+:ThreeDArray[1:1][1][1:1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1:1][1][1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1:1][1:1][1:1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1:1][1:1][1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1][1][1:1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1][1][1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1][1:1][1:1]) + while (1); +#pragma acc parallel reduction(+:ThreeDArray[1][1:1][1]) + while (1); } void inst() { diff --git a/clang/test/SemaOpenACC/loop-construct-reduction-clause.cpp b/clang/test/SemaOpenACC/loop-construct-reduction-clause.cpp index 2a07c2c..f2dd928 100644 --- a/clang/test/SemaOpenACC/loop-construct-reduction-clause.cpp +++ b/clang/test/SemaOpenACC/loop-construct-reduction-clause.cpp @@ -153,19 +153,17 @@ void uses() { CompositeHasComposite CoCArr[5]; // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of array type 'CompositeHasComposite'}} + // expected-note@+3{{used as element type of array type 'CompositeHasComposite[5]'}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc loop reduction(+:CoCArr) for(int i = 0; i < 5; ++i); - // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of array type 'CompositeHasComposite[5]'}} + // expected-error@+3{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc loop reduction(+:CoCArr[3]) for(int i = 0; i < 5; ++i); - // expected-error@+4{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} - // expected-note@+3{{used as element type of sub-array type 'CompositeHasComposite'}} + // expected-error@+3{{invalid type 'struct CompositeOfScalars' used in OpenACC 'reduction' variable reference; type is not a scalar value}} // expected-note@#COS_FIELD{{used as field 'COS' of composite 'CompositeHasComposite'}} // expected-note@+1{{OpenACC 'reduction' variable reference must be a scalar variable or a composite of scalars, or an array, sub-array, or element of scalar types}} #pragma acc loop reduction(+:CoCArr[1:1]) diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 0c05184..169b2d2 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h" +#include "clang/Analysis/Analyses/LifetimeSafety.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Testing/TestAST.h" @@ -63,7 +63,7 @@ public: Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr); Analysis->run(); - AnnotationToPointMap = Analysis->getFactManager().getTestPoints(); + AnnotationToPointMap = Analysis->getTestPoints(); } LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; } @@ -98,11 +98,10 @@ public: auto *VD = findDecl<ValueDecl>(VarName); if (!VD) return std::nullopt; - // This assumes the OriginManager's `get` can find an existing origin. - // We might need a `find` method on OriginManager to avoid `getOrCreate` - // logic in a const-query context if that becomes an issue. - return const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr()) - .get(*VD); + auto OID = Analysis.getOriginIDForDecl(VD); + if (!OID) + ADD_FAILURE() << "Origin for '" << VarName << "' not found."; + return OID; } std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) { @@ -111,10 +110,7 @@ public: ADD_FAILURE() << "Failed to find VarDecl for '" << VarName << "'"; return {}; } - std::vector<LoanID> LID; - for (const Loan &L : Analysis.getFactManager().getLoanMgr().getLoans()) - if (L.Path.D == VD) - LID.push_back(L.ID); + std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD); if (LID.empty()) { ADD_FAILURE() << "Loan for '" << VarName << "' not found."; return {}; @@ -127,7 +123,7 @@ public: ProgramPoint PP = Runner.getProgramPoint(Annotation); if (!PP) return std::nullopt; - return Analysis.getLoanPropagation().getLoans(OID, PP); + return Analysis.getLoansAtPoint(OID, PP); } std::optional<std::vector<std::pair<OriginID, LivenessKind>>> @@ -135,10 +131,7 @@ public: ProgramPoint PP = Runner.getProgramPoint(Annotation); if (!PP) return std::nullopt; - std::vector<std::pair<OriginID, LivenessKind>> Result; - for (auto &[OID, Info] : Analysis.getLiveOrigins().getLiveOriginsAt(PP)) - Result.push_back({OID, Info.Kind}); - return Result; + return Analysis.getLiveOriginsAtPoint(PP); } private: diff --git a/clang/www/c_status.html b/clang/www/c_status.html index a6bcd4c..b803962 100644 --- a/clang/www/c_status.html +++ b/clang/www/c_status.html @@ -349,7 +349,7 @@ conformance.</p> <tr> <td>Allow calling static inline within extern inline</td> <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3622.txt">N3622</a></td> - <td class="unknown" align="center">Unknown</td> + <td class="unreleased" align="center">Clang 22</td> </tr> <tr> <td>Generic replacement (v. 2 of quasi-literals)</td> |