Skip to content

Commit 6ad27da

Browse files
committed
[LifetimeSafety] Add loan expiry analysis
1 parent e6fc855 commit 6ad27da

File tree

3 files changed

+379
-10
lines changed

3 files changed

+379
-10
lines changed

clang/include/clang/Analysis/Analyses/LifetimeSafety.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace internal {
3333
class Fact;
3434
class FactManager;
3535
class LoanPropagationAnalysis;
36+
class ExpiredLoansAnalysis;
3637
struct LifetimeFactory;
3738

3839
/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
@@ -52,6 +53,10 @@ template <typename Tag> struct ID {
5253
IDBuilder.AddInteger(Value);
5354
}
5455
};
56+
template <typename Tag>
57+
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
58+
return OS << ID.Value;
59+
}
5560

5661
using LoanID = ID<struct LoanTag>;
5762
using OriginID = ID<struct OriginTag>;
@@ -81,6 +86,9 @@ class LifetimeSafetyAnalysis {
8186
/// Returns the set of loans an origin holds at a specific program point.
8287
LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
8388

89+
/// Returns the set of loans that have expired at a specific program point.
90+
LoanSet getExpiredLoansAtPoint(ProgramPoint PP) const;
91+
8492
/// Finds the OriginID for a given declaration.
8593
/// Returns a null optional if not found.
8694
std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const;
@@ -105,6 +113,7 @@ class LifetimeSafetyAnalysis {
105113
std::unique_ptr<LifetimeFactory> Factory;
106114
std::unique_ptr<FactManager> FactMgr;
107115
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
116+
std::unique_ptr<ExpiredLoansAnalysis> ExpiredLoans;
108117
};
109118
} // namespace internal
110119
} // namespace clang::lifetimes

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,10 @@
2323
#include "llvm/Support/Debug.h"
2424
#include "llvm/Support/TimeProfiler.h"
2525
#include <cstdint>
26+
#include <memory>
2627

2728
namespace clang::lifetimes {
2829
namespace internal {
29-
namespace {
30-
template <typename Tag>
31-
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
32-
return OS << ID.Value;
33-
}
34-
} // namespace
3530

3631
/// Represents the storage location being borrowed, e.g., a specific stack
3732
/// variable.
@@ -832,6 +827,91 @@ class LoanPropagationAnalysis
832827
}
833828
};
834829

830+
// ========================================================================= //
831+
// Expired Loans Analysis
832+
// ========================================================================= //
833+
834+
/// The dataflow lattice for tracking the set of expired loans.
835+
struct ExpiredLattice {
836+
LoanSet Expired;
837+
838+
ExpiredLattice() : Expired(nullptr) {};
839+
explicit ExpiredLattice(LoanSet S) : Expired(S) {}
840+
841+
bool operator==(const ExpiredLattice &Other) const {
842+
return Expired == Other.Expired;
843+
}
844+
bool operator!=(const ExpiredLattice &Other) const {
845+
return !(*this == Other);
846+
}
847+
848+
void dump(llvm::raw_ostream &OS) const {
849+
OS << "ExpiredLattice State:\n";
850+
if (Expired.isEmpty())
851+
OS << " <empty>\n";
852+
for (const LoanID &LID : Expired)
853+
OS << " Loan " << LID << " is expired\n";
854+
}
855+
};
856+
857+
/// The analysis that tracks which loans have expired.
858+
class ExpiredLoansAnalysis
859+
: public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice,
860+
Direction::Forward> {
861+
862+
LoanSet::Factory &Factory;
863+
864+
public:
865+
ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
866+
LifetimeFactory &Factory)
867+
: DataflowAnalysis(C, AC, F), Factory(Factory.LoanSetFactory) {}
868+
869+
using Base::transfer;
870+
871+
StringRef getAnalysisName() const { return "ExpiredLoans"; }
872+
873+
Lattice getInitialState() { return Lattice(Factory.getEmptySet()); }
874+
875+
/// Merges two lattices by taking the union of the expired loan sets.
876+
Lattice join(Lattice L1, Lattice L2) const {
877+
return Lattice(utils::join(L1.Expired, L2.Expired, Factory));
878+
}
879+
880+
Lattice transfer(Lattice In, const ExpireFact &F) {
881+
return Lattice(Factory.add(In.Expired, F.getLoanID()));
882+
}
883+
884+
// Removes the loan from the set of expired loans.
885+
//
886+
// When a loan is re-issued (e.g., in a loop), it is no longer considered
887+
// expired. A loan can be in the expired set at the point of issue due to
888+
// the dataflow state from a previous loop iteration being propagated along
889+
// a backedge in the CFG.
890+
//
891+
// Note: This has a subtle false-negative though where a loan from previous
892+
// iteration is not overwritten by a reissue. This needs careful tracking
893+
// of loans "across iterations" which can be considered for future
894+
// enhancements.
895+
//
896+
// void foo(int safe) {
897+
// int* p = &safe;
898+
// int* q = &safe;
899+
// while (condition()) {
900+
// int x = 1;
901+
// p = &x; // A loan to 'x' is issued to 'p' in every iteration.
902+
// if (condition()) {
903+
// q = p;
904+
// }
905+
// (void)*p; // OK — 'p' points to 'x' from new iteration.
906+
// (void)*q; // UaF - 'q' still points to 'x' from previous iteration
907+
// // which is now destroyed.
908+
// }
909+
// }
910+
Lattice transfer(Lattice In, const IssueFact &F) {
911+
return Lattice(Factory.remove(In.Expired, F.getLoanID()));
912+
}
913+
};
914+
835915
// ========================================================================= //
836916
// TODO:
837917
// - Modify loan expiry analysis to answer `bool isExpired(Loan L, Point P)`
@@ -873,6 +953,10 @@ void LifetimeSafetyAnalysis::run() {
873953
LoanPropagation =
874954
std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
875955
LoanPropagation->run();
956+
957+
ExpiredLoans =
958+
std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory);
959+
ExpiredLoans->run();
876960
}
877961

878962
LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
@@ -881,6 +965,11 @@ LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
881965
return LoanPropagation->getLoans(OID, PP);
882966
}
883967

968+
LoanSet LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const {
969+
assert(ExpiredLoans && "ExpiredLoansAnalysis has not been run.");
970+
return ExpiredLoans->getState(PP).Expired;
971+
}
972+
884973
std::optional<OriginID>
885974
LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const {
886975
assert(FactMgr && "FactManager not initialized");

0 commit comments

Comments
 (0)