https://github.com/usx95 created https://github.com/llvm/llvm-project/pull/149731
None >From 250f9d4735503dcd6eba12e500deca91342d9f48 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Sun, 20 Jul 2025 18:33:56 +0000 Subject: [PATCH] basic error report for use after free --- .../clang/Analysis/Analyses/LifetimeSafety.h | 52 +++- clang/include/clang/Basic/DiagnosticGroups.td | 5 +- .../clang/Basic/DiagnosticSemaKinds.td | 11 +- clang/lib/Analysis/LifetimeSafety.cpp | 248 ++++++++++++++---- clang/lib/Sema/AnalysisBasedWarnings.cpp | 35 ++- clang/test/Sema/warn-lifetime-safety.cpp | 204 ++++++++++++++ .../unittests/Analysis/LifetimeSafetyTest.cpp | 15 +- 7 files changed, 505 insertions(+), 65 deletions(-) create mode 100644 clang/test/Sema/warn-lifetime-safety.cpp diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h index 1c00558d32f63..bd7e76b1bc238 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h @@ -19,14 +19,35 @@ #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 { + 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); +void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter); namespace internal { // Forward declarations of internal types. @@ -53,6 +74,7 @@ template <typename Tag> struct ID { IDBuilder.AddInteger(Value); } }; + template <typename Tag> inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) { return OS << ID.Value; @@ -66,6 +88,7 @@ using OriginID = ID<struct OriginTag>; // TODO(opt): Consider using a bitset to represent the set of loans. using LoanSet = llvm::ImmutableSet<LoanID>; using OriginSet = llvm::ImmutableSet<OriginID>; +using ExpiredLoanMap = llvm::ImmutableMap<LoanID, const Fact *>; /// A `ProgramPoint` identifies a location in the CFG by pointing to a specific /// `Fact`. identified by a lifetime-related event (`Fact`). @@ -78,7 +101,8 @@ using ProgramPoint = const Fact *; /// encapsulates the various dataflow analyses. class LifetimeSafetyAnalysis { public: - LifetimeSafetyAnalysis(AnalysisDeclContext &AC); + LifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter); ~LifetimeSafetyAnalysis(); void run(); @@ -87,7 +111,7 @@ class LifetimeSafetyAnalysis { LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const; /// Returns the set of loans that have expired at a specific program point. - LoanSet getExpiredLoansAtPoint(ProgramPoint PP) const; + ExpiredLoanMap getExpiredLoansAtPoint(ProgramPoint PP) const; /// Finds the OriginID for a given declaration. /// Returns a null optional if not found. @@ -110,6 +134,7 @@ class LifetimeSafetyAnalysis { private: AnalysisDeclContext &AC; + LifetimeSafetyReporter *Reporter; std::unique_ptr<LifetimeFactory> Factory; std::unique_ptr<FactManager> FactMgr; std::unique_ptr<LoanPropagationAnalysis> LoanPropagation; @@ -118,4 +143,25 @@ class LifetimeSafetyAnalysis { } // 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/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index c28a919e35d08..257823e4664d0 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -533,7 +533,10 @@ def Dangling : DiagGroup<"dangling", [DanglingAssignment, DanglingGsl, ReturnStackAddress]>; -def LifetimeSafety : DiagGroup<"experimental-lifetime-safety">; +def LifetimeSafetyPermissive : DiagGroup<"experimental-lifetime-safety-permissive">; +def LifetimeSafetyStrict : DiagGroup<"experimental-lifetime-safety-strict">; +def LifetimeSafety : DiagGroup<"experimental-lifetime-safety", [LifetimeSafetyPermissive, + LifetimeSafetyStrict]>; def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">; def DllexportExplicitInstantiationDecl : DiagGroup<"dllexport-explicit-instantiation-decl">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 2781ff81ab4cf..875e17d273166 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10641,9 +10641,14 @@ def warn_dangling_reference_captured_by_unknown : Warning< "object whose reference is captured will be destroyed at the end of " "the full-expression">, InGroup<DanglingCapture>; -def warn_experimental_lifetime_safety_dummy_warning : Warning< - "todo: remove this warning after we have atleast one warning based on the lifetime analysis">, - InGroup<LifetimeSafety>, DefaultIgnore; +def warn_lifetime_safety_use_after_free_permissive : Warning< + "object whose reference is captured does not live long enough">, + InGroup<LifetimeSafetyPermissive>, DefaultIgnore; +def warn_lifetime_safety_use_after_free_strict : Warning< + "object whose reference is captured may not live long enough">, + InGroup<LifetimeSafetyStrict>, DefaultIgnore; +def note_lifetime_safety_used_here : Note<"later used here">; +def note_lifetime_safety_destroyed_here : Note<"destroyed here">; // For non-floating point, expressions of the form x == x or x != x // should result in a warning, since these always evaluate to a constant. diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp index 815a36e13412c..ea5fc04d51827 100644 --- a/clang/lib/Analysis/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -27,12 +27,6 @@ namespace clang::lifetimes { namespace internal { -namespace { -// template <typename Tag> -// inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) { -// return OS << ID.Value; -// } -} // namespace /// Represents the storage location being borrowed, e.g., a specific stack /// variable. @@ -51,10 +45,10 @@ struct Loan { /// is represented as empty LoanSet LoanID ID; AccessPath Path; - SourceLocation IssueLoc; + const Expr *IssueExpr; - Loan(LoanID id, AccessPath path, SourceLocation loc) - : ID(id), Path(path), IssueLoc(loc) {} + Loan(LoanID id, AccessPath path, const Expr *IssueExpr) + : ID(id), Path(path), IssueExpr(IssueExpr) {} }; /// An Origin is a symbolic identifier that represents the set of possible @@ -88,8 +82,8 @@ class LoanManager { public: LoanManager() = default; - Loan &addLoan(AccessPath Path, SourceLocation Loc) { - AllLoans.emplace_back(getNextLoanID(), Path, Loc); + Loan &addLoan(AccessPath Path, const Expr *IssueExpr) { + AllLoans.emplace_back(getNextLoanID(), Path, IssueExpr); return AllLoans.back(); } @@ -205,6 +199,8 @@ class Fact { AssignOrigin, /// An origin escapes the function by flowing into the return value. ReturnOfOrigin, + // A pointer is used (eg. dereferenced). + Use, /// A marker for a specific point in the code, for testing. TestPoint, }; @@ -248,12 +244,17 @@ class IssueFact : public Fact { class ExpireFact : public Fact { LoanID LID; + SourceLocation ExpiryLoc; public: static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; } - ExpireFact(LoanID LID) : Fact(Kind::Expire), LID(LID) {} + 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 override { OS << "Expire (LoanID: " << getLoanID() << ")\n"; } @@ -293,6 +294,24 @@ class ReturnOfOriginFact : public Fact { } }; +class UseFact : public Fact { + OriginID UsedOrigin; + const Expr *UseExpr; + +public: + static bool classof(const Fact *F) { return F->getKind() == Kind::Use; } + + UseFact(OriginID UsedOrigin, const Expr *UseExpr) + : Fact(Kind::Use), UsedOrigin(UsedOrigin), UseExpr(UseExpr) {} + + OriginID getUsedOrigin() const { return UsedOrigin; } + const Expr *getUseExpr() const { return UseExpr; } + + void dump(llvm::raw_ostream &OS) const override { + OS << "Use (OriginID: " << UsedOrigin << ")\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 { @@ -423,13 +442,17 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { if (VD->hasLocalStorage()) { OriginID OID = FactMgr.getOriginMgr().getOrCreate(*UO); AccessPath AddrOfLocalVarPath(VD); - const Loan &L = FactMgr.getLoanMgr().addLoan(AddrOfLocalVarPath, - UO->getOperatorLoc()); + const Loan &L = + FactMgr.getLoanMgr().addLoan(AddrOfLocalVarPath, UO); CurrentBlockFacts.push_back( FactMgr.createFact<IssueFact>(L.ID, OID)); } } } + } else if (UO->getOpcode() == UO_Deref) { + // This is a pointer use, like '*p'. + OriginID OID = FactMgr.getOriginMgr().get(*UO->getSubExpr()); + CurrentBlockFacts.push_back(FactMgr.createFact<UseFact>(OID, UO)); } } @@ -498,7 +521,8 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { // 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)); + CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>( + L.ID, DtorOpt.getTriggerStmt()->getEndLoc())); } } @@ -622,6 +646,7 @@ class DataflowAnalysis { } } +protected: Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); } Lattice getInState(const CFGBlock *B) const { return InStates.lookup(B); } @@ -669,6 +694,8 @@ class DataflowAnalysis { return D->transfer(In, *F->getAs<AssignOriginFact>()); 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>()); } @@ -680,6 +707,7 @@ class DataflowAnalysis { Lattice transfer(Lattice In, const ExpireFact &) { return In; } Lattice transfer(Lattice In, const AssignOriginFact &) { 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; } }; @@ -697,14 +725,38 @@ static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, return A; } +/// Checks if set A is a subset of set B. +template <typename T> +static bool isSubsetOf(const llvm::ImmutableSet<T> &A, + const llvm::ImmutableSet<T> &B) { + if (A.isEmpty()) + return true; + + for (const T &Elem : A) + if (!B.contains(Elem)) + return false; + return true; +} + +template <typename T> +static llvm::ImmutableSet<T> +getIntersection(const llvm::ImmutableSet<T> &A, const llvm::ImmutableSet<T> &B, + typename llvm::ImmutableSet<T>::Factory &F) { + llvm::ImmutableSet<T> Result = F.getEmptySet(); + for (const T &Elem : A) + if (B.contains(Elem)) + Result = F.add(Result, Elem); + return Result; +} + /// 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> +template <typename K, typename V, typename JoinerT> static llvm::ImmutableMap<K, V> join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B, - typename llvm::ImmutableMap<K, V>::Factory &F, Joiner joinValues) { + typename llvm::ImmutableMap<K, V>::Factory &F, JoinerT Joiner) { if (A.getHeight() < B.getHeight()) std::swap(A, B); @@ -714,7 +766,7 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B, const K &Key = Entry.first; const V &ValB = Entry.second; if (const V *ValA = A.lookup(Key)) - A = F.add(A, Key, joinValues(*ValA, ValB)); + A = F.add(A, Key, Joiner(*ValA, ValB)); else A = F.add(A, Key, ValB); } @@ -733,11 +785,7 @@ using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>; struct LifetimeFactory { OriginLoanMap::Factory OriginMapFactory; LoanSet::Factory LoanSetFactory; - - /// Creates a singleton set containing only the given loan ID. - LoanSet createLoanSet(LoanID LID) { - return LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID); - } + ExpiredLoanMap::Factory ExpiredLoanMapFactory; }; /// Represents the dataflow lattice for loan propagation. @@ -778,13 +826,15 @@ struct LoanPropagationLattice { class LoanPropagationAnalysis : public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice, Direction::Forward> { - - LifetimeFactory &Factory; + OriginLoanMap::Factory &OriginLoanMapFactory; + LoanSet::Factory &LoanSetFactory; public: LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - LifetimeFactory &Factory) - : DataflowAnalysis(C, AC, F), Factory(Factory) {} + LifetimeFactory &LFactory) + : DataflowAnalysis(C, AC, F), + OriginLoanMapFactory(LFactory.OriginMapFactory), + LoanSetFactory(LFactory.LoanSetFactory) {} using Base::transfer; @@ -796,9 +846,9 @@ class LoanPropagationAnalysis // 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, Factory.OriginMapFactory, - [this](LoanSet S1, LoanSet S2) { - return utils::join(S1, S2, Factory.LoanSetFactory); + utils::join(A.Origins, B.Origins, OriginLoanMapFactory, + [&](LoanSet S1, LoanSet S2) { + return utils::join(S1, S2, LoanSetFactory); }); return Lattice(JoinedOrigins); } @@ -807,8 +857,9 @@ class LoanPropagationAnalysis Lattice transfer(Lattice In, const IssueFact &F) { OriginID OID = F.getOriginID(); LoanID LID = F.getLoanID(); - return LoanPropagationLattice(Factory.OriginMapFactory.add( - In.Origins, OID, Factory.createLoanSet(LID))); + return LoanPropagationLattice(OriginLoanMapFactory.add( + In.Origins, OID, + LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID))); } /// The destination origin's loan set is replaced by the source's. @@ -818,7 +869,7 @@ class LoanPropagationAnalysis OriginID SrcOID = F.getSrcOriginID(); LoanSet SrcLoans = getLoans(In, SrcOID); return LoanPropagationLattice( - Factory.OriginMapFactory.add(In.Origins, DestOID, SrcLoans)); + OriginLoanMapFactory.add(In.Origins, DestOID, SrcLoans)); } LoanSet getLoans(OriginID OID, ProgramPoint P) { @@ -829,7 +880,7 @@ class LoanPropagationAnalysis LoanSet getLoans(Lattice L, OriginID OID) { if (auto *Loans = L.Origins.lookup(OID)) return *Loans; - return Factory.LoanSetFactory.getEmptySet(); + return LoanSetFactory.getEmptySet(); } }; @@ -839,10 +890,10 @@ class LoanPropagationAnalysis /// The dataflow lattice for tracking the set of expired loans. struct ExpiredLattice { - LoanSet Expired; + ExpiredLoanMap Expired; ExpiredLattice() : Expired(nullptr) {}; - explicit ExpiredLattice(LoanSet S) : Expired(S) {} + explicit ExpiredLattice(ExpiredLoanMap M) : Expired(M) {} bool operator==(const ExpiredLattice &Other) const { return Expired == Other.Expired; @@ -855,8 +906,8 @@ struct ExpiredLattice { OS << "ExpiredLattice State:\n"; if (Expired.isEmpty()) OS << " <empty>\n"; - for (const LoanID &LID : Expired) - OS << " Loan " << LID << " is expired\n"; + for (const auto &Entry : Expired) + OS << " Loan " << Entry.first << " is expired\n"; } }; @@ -865,31 +916,119 @@ class ExpiredLoansAnalysis : public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice, Direction::Forward> { - LoanSet::Factory &Factory; + ExpiredLoanMap::Factory &Factory; public: ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - LifetimeFactory &Factory) - : DataflowAnalysis(C, AC, F), Factory(Factory.LoanSetFactory) {} + LifetimeFactory &LFactory) + : DataflowAnalysis(C, AC, F), Factory(LFactory.ExpiredLoanMapFactory) {} using Base::transfer; StringRef getAnalysisName() const { return "ExpiredLoans"; } - Lattice getInitialState() { return Lattice(Factory.getEmptySet()); } + Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); } /// Merges two lattices by taking the union of the expired loan sets. - Lattice join(Lattice L1, Lattice L2) const { - return Lattice(utils::join(L1.Expired, L2.Expired, Factory)); + Lattice join(Lattice L1, Lattice L2) { + return Lattice(utils::join(L1.Expired, L2.Expired, Factory, + [](const Fact *F, const Fact *) { return F; })); } Lattice transfer(Lattice In, const ExpireFact &F) { - return Lattice(Factory.add(In.Expired, F.getLoanID())); + return Lattice(Factory.add(In.Expired, F.getLoanID(), &F)); } Lattice transfer(Lattice In, const IssueFact &F) { return Lattice(Factory.remove(In.Expired, F.getLoanID())); } + + ExpiredLoanMap getExpiredLoans(ProgramPoint P) { return getState(P).Expired; } +}; + +// ========================================================================= // +// Error Reporting Infrastructure +// ========================================================================= // + +/// Struct to store the complete context for a potential lifetime violation. +struct PendingWarning { + const Expr *IssueExpr; // Where the loan was originally created. + SourceLocation ExpiryLoc; // Where the variable went out of scope. + const Expr *UseExpr; // Where the pointer was dereferenced. + Confidence Level; +}; + +class LifetimeChecker { +private: + llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; + LoanPropagationAnalysis &LoanPropagation; + ExpiredLoansAnalysis &ExpiredLoans; + FactManager &FactMgr; + AnalysisDeclContext &ADC; + LifetimeSafetyReporter *Reporter; + +public: + LifetimeChecker(LoanPropagationAnalysis &LPA, ExpiredLoansAnalysis &ELA, + FactManager &FM, AnalysisDeclContext &ADC, + LifetimeSafetyReporter *Reporter) + : LoanPropagation(LPA), ExpiredLoans(ELA), 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 *UF = F->getAs<UseFact>()) + checkUse(UF); + issuePendingWarnings(); + } + + void checkUse(const UseFact *UF) { + OriginID O = UF->getUsedOrigin(); + LoanSet HeldLoans = LoanPropagation.getLoans(O, UF); + ExpiredLoanMap AllExpiredLoans = ExpiredLoans.getExpiredLoans(UF); + if (HeldLoans.isEmpty() || AllExpiredLoans.isEmpty()) + return; + + llvm::SmallVector<LoanID> DefaultedLoans; + bool IsDefiniteError = true; + for (LoanID L : HeldLoans) { + if (AllExpiredLoans.contains(L)) + DefaultedLoans.push_back(L); + else + IsDefiniteError = false; + } + if (DefaultedLoans.empty()) + return; + Confidence CurrentConfidence = + IsDefiniteError ? Confidence::Definite : Confidence::Maybe; + for (LoanID BadLoan : DefaultedLoans) { + + if (FinalWarningsMap.count(BadLoan) && + CurrentConfidence <= FinalWarningsMap[BadLoan].Level) + continue; + + const Loan &L = FactMgr.getLoanMgr().getLoan(BadLoan); + auto *EF = AllExpiredLoans.lookup(BadLoan); + assert(EF && "Could not find ExpireFact for a problematic loan."); + + const Expr *IssueExpr = L.IssueExpr; + SourceLocation ExpiryLoc = dyn_cast<ExpireFact>(*EF)->getExpiryLoc(); + + FinalWarningsMap[BadLoan] = {IssueExpr, ExpiryLoc, UF->getUseExpr(), + CurrentConfidence}; + } + } + + void issuePendingWarnings() { + if (!Reporter) + return; + for (const auto &pair : FinalWarningsMap) { + const PendingWarning &PW = pair.second; + Reporter->reportUseAfterFree(PW.IssueExpr, PW.UseExpr, PW.ExpiryLoc, + PW.Level); + } + } }; // ========================================================================= // @@ -906,8 +1045,9 @@ class ExpiredLoansAnalysis // We need this here for unique_ptr with forward declared class. LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default; -LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC) - : AC(AC), Factory(std::make_unique<LifetimeFactory>()), +LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter) + : AC(AC), Reporter(Reporter), Factory(std::make_unique<LifetimeFactory>()), FactMgr(std::make_unique<FactManager>()) {} void LifetimeSafetyAnalysis::run() { @@ -937,6 +1077,10 @@ void LifetimeSafetyAnalysis::run() { ExpiredLoans = std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory); ExpiredLoans->run(); + + LifetimeChecker Checker(*LoanPropagation, *ExpiredLoans, *FactMgr, AC, + Reporter); + Checker.run(); } LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID, @@ -945,9 +1089,10 @@ LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID, return LoanPropagation->getLoans(OID, PP); } -LoanSet LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const { +ExpiredLoanMap +LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const { assert(ExpiredLoans && "ExpiredLoansAnalysis has not been run."); - return ExpiredLoans->getState(PP).Expired; + return ExpiredLoans->getExpiredLoans(PP); } std::optional<OriginID> @@ -987,8 +1132,9 @@ llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const { } } // namespace internal -void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC) { - internal::LifetimeSafetyAnalysis Analysis(AC); +void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, + LifetimeSafetyReporter *Reporter) { + internal::LifetimeSafetyAnalysis Analysis(AC, Reporter); Analysis.run(); } } // namespace clang::lifetimes diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 89c5a3596f584..058c1e8135301 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2780,6 +2780,33 @@ class CallableVisitor : public DynamicRecursiveASTVisitor { } }; +namespace { + +class LifetimeSafetyReporterImpl + : public clang::lifetimes::LifetimeSafetyReporter { + +public: + LifetimeSafetyReporterImpl(Sema &S) : S(S) {} + + void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, + SourceLocation FreeLoc, + lifetimes::Confidence Confidence) override { + unsigned DiagID; + if (Confidence == lifetimes::Confidence::Definite) + DiagID = diag::warn_lifetime_safety_use_after_free_permissive; + else // Confidence::Maybe + DiagID = diag::warn_lifetime_safety_use_after_free_strict; + S.Diag(IssueExpr->getExprLoc(), DiagID) << IssueExpr->getEndLoc(); + S.Diag(FreeLoc, diag::note_lifetime_safety_destroyed_here); + S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here) + << UseExpr->getEndLoc(); + } + +private: + Sema &S; +}; +} // namespace + void clang::sema::AnalysisBasedWarnings::IssueWarnings( TranslationUnitDecl *TU) { if (!TU) @@ -2903,7 +2930,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( } bool EnableLifetimeSafetyAnalysis = !Diags.isIgnored( - diag::warn_experimental_lifetime_safety_dummy_warning, D->getBeginLoc()); + diag::warn_lifetime_safety_use_after_free_permissive, D->getBeginLoc()); // Install the logical handler. std::optional<LogicalErrorHandler> LEH; if (LogicalErrorHandler::hasActiveDiagnostics(Diags, D->getBeginLoc())) { @@ -3030,8 +3057,10 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( // TODO: Enable lifetime safety analysis for other languages once it is // stable. if (EnableLifetimeSafetyAnalysis && S.getLangOpts().CPlusPlus) { - if (AC.getCFG()) - lifetimes::runLifetimeSafetyAnalysis(AC); + if (AC.getCFG()) { + LifetimeSafetyReporterImpl LifetimeSafetyReporter(S); + lifetimes::runLifetimeSafetyAnalysis(AC, &LifetimeSafetyReporter); + } } // Check for violations of "called once" parameter properties. if (S.getLangOpts().ObjC && !S.getLangOpts().CPlusPlus && diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp new file mode 100644 index 0000000000000..f4b4b160b4410 --- /dev/null +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -0,0 +1,204 @@ +// RUN: %clang_cc1 -fsyntax-only -Wexperimental-lifetime-safety -verify %s + +struct MyObj { + int id; + ~MyObj() {} // Non-trivial destructor + MyObj operator+(MyObj); +}; + +//===----------------------------------------------------------------------===// +// Basic Definite Use-After-Free (-W...permissive) +// These are cases where the pointer is guaranteed to be dangling at the use site. +//===----------------------------------------------------------------------===// + +void definite_simple_case() { + MyObj* p; + { + MyObj s; + p = &s; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void definite_pointer_chain() { + MyObj* p; + MyObj* q; + { + MyObj s; + p = &s; // expected-warning {{does not live long enough}} + q = p; + } // expected-note {{destroyed here}} + (void)*q; // expected-note {{later used here}} +} + +void definite_multiple_uses_one_warning() { + MyObj* p; + { + MyObj s; + p = &s; // expected-warning {{does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} + // No second warning for the same loan. + p->id = 1; + MyObj* q = p; + (void)*q; +} + +//===----------------------------------------------------------------------===// +// Potential (Maybe) Use-After-Free (-W...strict) +// These are cases where the pointer *may* become dangling, depending on the path taken. +//===----------------------------------------------------------------------===// + +void potential_if_branch(bool cond) { + MyObj safe; + MyObj* p = &safe; + if (cond) { + MyObj temp; + p = &temp; // expected-warning {{object whose reference is captured may not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void potential_else_branch(bool cond) { + MyObj safe; + MyObj* p = &safe; + if (cond) { + MyObj temp; + p = &temp; // expected-warning {{may not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +// If all paths lead to a dangle, it becomes a definite error. +void potential_becomes_definite(bool cond) { + MyObj* p; + if (cond) { + MyObj temp1; + p = &temp1; // expected-warning {{does not live long enough}} + } // expected-note {{destroyed here}} + else { + MyObj temp2; + p = &temp2; // expected-warning {{does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note 2 {{later used here}} +} + +void definite_potential_together(bool cond) { + MyObj safe; + MyObj* p_maybe = &safe; + MyObj* p_definite = nullptr; + + { + MyObj s; + p_definite = &s; // expected-warning {{does not live long enough}} + if (cond) { + p_maybe = &s; // expected-warning {{may not live long enough}} + } + } // expected-note 2 {{destroyed here}} + (void)*p_definite; // expected-note {{later used here}} + (void)*p_maybe; // expected-note {{later used here}} +} + +void definite_overrides_potential(bool cond) { + MyObj safe; + MyObj* p; + MyObj* q; + { + MyObj s; + q = &s; // expected-warning {{does not live long enough}} + p = q; + } // expected-note {{destroyed here}} + + if (cond) { + // 'q' is conditionally "rescued". 'p' is not. + q = &safe; + } + + // The use of 'p' is a definite error because it was never rescued. + (void)*q; + (void)*p; // expected-note {{later used here}} + (void)*q; +} + + +//===----------------------------------------------------------------------===// +// Control Flow Tests +//===----------------------------------------------------------------------===// + +void definite_for_loop() { + MyObj* p = nullptr; + for (int i = 0; i < 1; ++i) { + MyObj s; + p = &s; // expected-warning {{does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} + +void potential_for_loop_use_before_free(MyObj safe) { + MyObj* p = &safe; + for (int i = 0; i < 1; ++i) { + (void)*p; // expected-note {{later used here}} + MyObj s; + p = &s; // expected-warning {{may not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; +} + +void potential_loop_with_break(bool cond) { + MyObj safe; + MyObj* p = &safe; + for (int i = 0; i < 10; ++i) { + if (cond) { + MyObj temp; + p = &temp; // expected-warning {{may not live long enough}} + break; // expected-note {{destroyed here}} + } + } + (void)*p; // expected-note {{later used here}} +} + +void potential_switch(int mode) { + MyObj safe; + MyObj* p = &safe; + switch (mode) { + case 1: { + MyObj temp; + p = &temp; // expected-warning {{object whose reference is captured may not live long enough}} + break; // expected-note {{destroyed here}} + } + case 2: { + p = &safe; // This path is okay. + break; + } + } + (void)*p; // expected-note {{later used here}} +} + +//===----------------------------------------------------------------------===// +// No-Error Cases +//===----------------------------------------------------------------------===// +void no_error_dangle_then_rescue() { + MyObj safe; + MyObj* p; + { + MyObj temp; + p = &temp; // p is temporarily dangling. + } + p = &safe; // p is "rescued" before use. + (void)*p; // This is safe. +} + +// MyObj some_name(bool condition, MyObj x) { +// MyObj* p = &x; +// MyObj* q = &x; +// if (condition) +// { +// MyObj y{20}; +// MyObj * abcd = &y; +// p = abcd; +// q = abcd; +// } +// MyObj a = *p; +// MyObj b = *q; +// return a + b; +// } \ No newline at end of file diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index a48fcfd9865a8..1a7bea3a44370 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -33,7 +33,9 @@ class LifetimeTestRunner { )"; FullCode += Code.str(); - TestAST = std::make_unique<clang::TestAST>(FullCode); + Inputs = TestInputs(FullCode); + Inputs.Language = TestLanguage::Lang_CXX20; + TestAST = std::make_unique<clang::TestAST>(Inputs); ASTCtx = &TestAST->context(); // Find the target function using AST matchers. @@ -51,7 +53,7 @@ class LifetimeTestRunner { BuildOptions.AddTemporaryDtors = true; // Run the main analysis. - Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx); + Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr); Analysis->run(); AnnotationToPointMap = Analysis->getTestPoints(); @@ -70,6 +72,7 @@ class LifetimeTestRunner { } private: + TestInputs Inputs; std::unique_ptr<TestAST> TestAST; ASTContext *ASTCtx = nullptr; std::unique_ptr<AnalysisDeclContext> AnalysisCtx; @@ -118,11 +121,15 @@ class LifetimeTestHelper { return Analysis.getLoansAtPoint(OID, PP); } - std::optional<LoanSet> getExpiredLoansAtPoint(llvm::StringRef Annotation) { + std::optional<llvm::DenseSet<LoanID>> + getExpiredLoansAtPoint(llvm::StringRef Annotation) { ProgramPoint PP = Runner.getProgramPoint(Annotation); if (!PP) return std::nullopt; - return Analysis.getExpiredLoansAtPoint(PP); + llvm::DenseSet<LoanID> Result; + for (auto &L : Analysis.getExpiredLoansAtPoint(PP)) + Result.insert(L.first); + return Result; } private: _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits