[llvm-branch-commits] [clang] [LifetimeSafety] Implement a basic use-after-free diagnostic (PR #149731)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jul 21 07:48:55 PDT 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/149731
>From 8e3c34b2cc04ae149036cb85897d4f2ed89cf76a Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at 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 | 266 ++++++++++++++----
clang/lib/Sema/AnalysisBasedWarnings.cpp | 35 ++-
clang/test/Sema/warn-lifetime-safety.cpp | 204 ++++++++++++++
.../unittests/Analysis/LifetimeSafetyTest.cpp | 15 +-
7 files changed, 525 insertions(+), 63 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 94b8197bbf6f3..c2b31ffc98c2d 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -45,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
@@ -82,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();
}
@@ -199,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,
};
@@ -242,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";
}
@@ -287,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 {
@@ -417,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));
}
}
@@ -492,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()));
}
}
@@ -616,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); }
@@ -663,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>());
}
@@ -674,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; }
};
@@ -691,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);
@@ -708,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);
}
@@ -727,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.
@@ -772,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;
@@ -790,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);
}
@@ -801,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.
@@ -812,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) {
@@ -823,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();
}
};
@@ -833,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;
@@ -849,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";
}
};
@@ -859,26 +916,27 @@ 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));
}
// Removes the loan from the set of expired loans.
@@ -910,15 +968,118 @@ class ExpiredLoansAnalysis
Lattice transfer(Lattice In, const IssueFact &F) {
return Lattice(Factory.remove(In.Expired, F.getLoanID()));
}
+
+ ExpiredLoanMap getExpiredLoans(ProgramPoint P) { return getState(P).Expired; }
};
// ========================================================================= //
-// TODO:
-// - Modify loan expiry analysis to answer `bool isExpired(Loan L, Point P)`
-// - Modify origin liveness analysis to answer `bool isLive(Origin O, Point P)`
-// - Using the above three to perform the final error reporting.
+// Checker and Error reporter
// ========================================================================= //
+/// 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();
+ }
+
+ /// Checks for use-after-free errors for a given use of an Origin.
+ ///
+ /// This method is called for each 'UseFact' identified in the control flow
+ /// graph. It determines if the loans held by the used origin have expired
+ /// at the point of use.
+ void checkUse(const UseFact *UF) {
+
+ OriginID O = UF->getUsedOrigin();
+
+ // Get the set of loans that the origin might hold at this program point.
+ LoanSet HeldLoans = LoanPropagation.getLoans(O, UF);
+
+ // Get the set of all loans that have expired at this program point.
+ ExpiredLoanMap AllExpiredLoans = ExpiredLoans.getExpiredLoans(UF);
+
+ // If the pointer holds no loans or no loans have expired, there's nothing
+ // to check.
+ if (HeldLoans.isEmpty() || AllExpiredLoans.isEmpty())
+ return;
+
+ // Identify loans that are both held by the pointer and have expired.
+ llvm::SmallVector<LoanID> DefaultedLoans;
+ bool IsDefiniteError = true;
+ for (LoanID L : HeldLoans) {
+ if (AllExpiredLoans.contains(L))
+ DefaultedLoans.push_back(L);
+ else
+ // If at least one loan is not expired, this is not a definite error.
+ IsDefiniteError = false;
+ }
+
+ // If there are no defaulted loans, the use is safe.
+ if (DefaultedLoans.empty())
+ return;
+
+ // Determine the confidence level of the error (definite or maybe).
+ Confidence CurrentConfidence =
+ IsDefiniteError ? Confidence::Definite : Confidence::Maybe;
+
+ // For each expired loan, create a pending warning.
+ for (LoanID DefaultedLoan : DefaultedLoans) {
+
+ // If we already have a warning for this loan with a higher or equal
+ // confidence, skip this one.
+ if (FinalWarningsMap.count(DefaultedLoan) &&
+ CurrentConfidence <= FinalWarningsMap[DefaultedLoan].Level)
+ continue;
+
+ const Loan &L = FactMgr.getLoanMgr().getLoan(DefaultedLoan);
+ auto *EF = AllExpiredLoans.lookup(DefaultedLoan);
+ assert(EF && "Could not find ExpireFact for a problematic loan.");
+
+ const Expr *IssueExpr = L.IssueExpr;
+ SourceLocation ExpiryLoc = dyn_cast<ExpireFact>(*EF)->getExpiryLoc();
+
+ FinalWarningsMap[DefaultedLoan] = {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);
+ }
+ }
+};
+
// ========================================================================= //
// LifetimeSafetyAnalysis Class Implementation
// ========================================================================= //
@@ -926,8 +1087,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() {
@@ -957,6 +1119,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,
@@ -965,9 +1131,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>
@@ -1007,8 +1174,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:
More information about the llvm-branch-commits
mailing list