Skip to content

Commit f914426

Browse files
committed
lifetime-safety-multi-origin
1 parent 3c87119 commit f914426

File tree

17 files changed

+606
-284
lines changed

17 files changed

+606
-284
lines changed

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,19 @@ class ReturnOfOriginFact : public Fact {
152152

153153
class UseFact : public Fact {
154154
const Expr *UseExpr;
155-
OriginID OID;
155+
// The origins of the expression being used.
156+
llvm::SmallVector<OriginID, 1> OIDs;
156157
// True if this use is a write operation (e.g., left-hand side of assignment).
157158
// Write operations are exempted from use-after-free checks.
158159
bool IsWritten = false;
159160

160161
public:
161162
static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
162163

163-
UseFact(const Expr *UseExpr, OriginManager &OM)
164-
: Fact(Kind::Use), UseExpr(UseExpr), OID(OM.get(*UseExpr)) {}
164+
UseFact(const Expr *UseExpr, llvm::ArrayRef<OriginID> OIDs)
165+
: Fact(Kind::Use), UseExpr(UseExpr), OIDs(OIDs.begin(), OIDs.end()) {}
165166

166-
OriginID getUsedOrigin() const { return OID; }
167+
llvm::ArrayRef<OriginID> getUsedOrigins() const { return OIDs; }
167168
const Expr *getUseExpr() const { return UseExpr; }
168169
void markAsWritten() { IsWritten = true; }
169170
bool isWritten() const { return IsWritten; }
@@ -191,8 +192,8 @@ class TestPointFact : public Fact {
191192

192193
class FactManager {
193194
public:
194-
void init(const CFG &Cfg) {
195-
assert(BlockToFacts.empty() && "FactManager already initialized");
195+
FactManager(const AnalysisDeclContext &AC, const CFG &Cfg)
196+
: OriginMgr(AC.getASTContext()) {
196197
BlockToFacts.resize(Cfg.getNumBlockIDs());
197198
}
198199

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
5050
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
5151

5252
private:
53+
OriginTree *getTree(const ValueDecl &D);
54+
OriginTree *getTree(const Expr &E);
55+
56+
void flow(OriginTree *Dst, OriginTree *Src, bool Kill);
57+
5358
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt);
5459

5560
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
@@ -64,32 +69,24 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
6469

6570
template <typename Destination, typename Source>
6671
void flowOrigin(const Destination &D, const Source &S) {
67-
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
68-
OriginID SrcOID = FactMgr.getOriginMgr().get(S);
69-
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
70-
DestOID, SrcOID, /*KillDest=*/false));
72+
flow(getTree(D), getTree(S), /*Kill=*/false);
7173
}
7274

7375
template <typename Destination, typename Source>
7476
void killAndFlowOrigin(const Destination &D, const Source &S) {
75-
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
76-
OriginID SrcOID = FactMgr.getOriginMgr().get(S);
77-
CurrentBlockFacts.push_back(
78-
FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID, /*KillDest=*/true));
77+
flow(getTree(D), getTree(S), /*Kill=*/true);
7978
}
8079

8180
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
8281
/// If so, creates a `TestPointFact` and returns true.
8382
bool handleTestPoint(const CXXFunctionalCastExpr *FCE);
8483

85-
void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);
86-
8784
// A DeclRefExpr will be treated as a use of the referenced decl. It will be
8885
// checked for use-after-free unless it is later marked as being written to
8986
// (e.g. on the left-hand side of an assignment).
9087
void handleUse(const DeclRefExpr *DRE);
9188

92-
void markUseAsWrite(const DeclRefExpr *DRE);
89+
void markUseAsWrite(const Expr *E);
9390

9491
FactManager &FactMgr;
9592
AnalysisDeclContext &AC;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
3838
/// method or because it's a normal assignment operator.
3939
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
4040

41+
// Tells whether the type is annotated with [[gsl::Pointer]].
42+
bool isGslPointerType(QualType QT);
43+
// Tells whether the type is annotated with [[gsl::Owner]].
44+
bool isGslOwnerType(QualType QT);
45+
4146
} // namespace clang::lifetimes
4247

4348
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ class LifetimeSafetyAnalysis {
7171
return *LoanPropagation;
7272
}
7373
LiveOriginsAnalysis &getLiveOrigins() const { return *LiveOrigins; }
74-
FactManager &getFactManager() { return FactMgr; }
74+
FactManager &getFactManager() { return *FactMgr; }
7575

7676
private:
7777
AnalysisDeclContext &AC;
7878
LifetimeSafetyReporter *Reporter;
7979
LifetimeFactory Factory;
80-
FactManager FactMgr;
80+
std::unique_ptr<FactManager> FactMgr;
8181
std::unique_ptr<LiveOriginsAnalysis> LiveOrigins;
8282
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
8383
};

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

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,10 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
2828

2929
/// An Origin is a symbolic identifier that represents the set of possible
3030
/// loans a pointer-like object could hold at any given time.
31-
/// TODO: Enhance the origin model to handle complex types, pointer
32-
/// indirection and reborrowing. The plan is to move from a single origin per
33-
/// variable/expression to a "list of origins" governed by the Type.
34-
/// For example, the type 'int**' would have two origins.
35-
/// See discussion:
36-
/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238
31+
///
32+
/// Each Origin corresponds to a single level of indirection. For complex types
33+
/// with multiple levels of indirection (e.g., `int**`), multiple Origins are
34+
/// organized into an OriginTree structure (see below).
3735
struct Origin {
3836
OriginID ID;
3937
/// A pointer to the AST node that this origin represents. This union
@@ -52,41 +50,110 @@ struct Origin {
5250
}
5351
};
5452

53+
/// A tree of origins representing levels of indirection for pointer-like types.
54+
///
55+
/// Each node in the tree contains an OriginID representing a level of
56+
/// indirection. The tree structure captures the multi-level nature of
57+
/// pointer and reference types in the lifetime analysis.
58+
///
59+
/// Examples:
60+
/// - For `int& x`, the tree has depth 2:
61+
/// * Root: origin for the reference storage itself (the lvalue `x`)
62+
/// * Pointee: origin for what `x` refers to
63+
///
64+
/// - For `int* p`, the tree has depth 2:
65+
/// * Root: origin for the pointer variable `p`
66+
/// * Pointee: origin for what `p` points to
67+
///
68+
/// - For `View v` (where View is gsl::Pointer), the tree has depth 2:
69+
/// * Root: origin for the view object itself
70+
/// * Pointee: origin for what the view refers to
71+
///
72+
/// - For `int** pp`, the tree has depth 3:
73+
/// * Root: origin for `pp` itself
74+
/// * Pointee: origin for `*pp` (what `pp` points to)
75+
/// * Pointee->Pointee: origin for `**pp` (what `*pp` points to)
76+
///
77+
/// The tree structure enables the analysis to track how loans flow through
78+
/// different levels of indirection when assignments and dereferences occur.
79+
struct OriginTree {
80+
OriginID OID;
81+
OriginTree *Pointee = nullptr;
82+
83+
OriginTree(OriginID OID) : OID(OID) {}
84+
85+
size_t getDepth() const {
86+
size_t Depth = 1;
87+
const OriginTree *T = this;
88+
while (T->Pointee) {
89+
T = T->Pointee;
90+
Depth++;
91+
}
92+
return Depth;
93+
}
94+
};
95+
96+
bool isPointerLikeType(QualType QT);
97+
5598
/// Manages the creation, storage, and retrieval of origins for pointer-like
5699
/// variables and expressions.
57100
class OriginManager {
58101
public:
59-
OriginManager() = default;
60-
61-
Origin &addOrigin(OriginID ID, const clang::ValueDecl &D);
62-
Origin &addOrigin(OriginID ID, const clang::Expr &E);
63-
64-
// TODO: Mark this method as const once we remove the call to getOrCreate.
65-
OriginID get(const Expr &E);
66-
67-
OriginID get(const ValueDecl &D);
68102

69-
OriginID getOrCreate(const Expr &E);
103+
explicit OriginManager(ASTContext& AST) : AST(AST) {}
104+
105+
/// Gets or creates the OriginTree for a given ValueDecl.
106+
///
107+
/// Creates a tree structure mirroring the levels of indirection in the
108+
/// declaration's type (e.g., `int** p` creates depth 2).
109+
///
110+
/// \returns The OriginTree, or nullptr if the type is not pointer-like.
111+
OriginTree *getOrCreateTree(const ValueDecl *D);
112+
113+
/// Gets or creates the OriginTree for a given Expr.
114+
///
115+
/// Creates a tree based on the expression's type and value category:
116+
/// - Lvalues get an implicit reference level (modeling addressability)
117+
/// - Rvalues of non-pointer type return nullptr (no trackable origin)
118+
/// - DeclRefExpr may reuse the underlying declaration's tree
119+
///
120+
/// \returns The OriginTree, or nullptr for non-pointer rvalues.
121+
OriginTree *getOrCreateTree(const Expr *E);
70122

71123
const Origin &getOrigin(OriginID ID) const;
72124

73125
llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }
74126

75-
OriginID getOrCreate(const ValueDecl &D);
76-
77127
unsigned getNumOrigins() const { return NextOriginID.Value; }
78128

79129
void dump(OriginID OID, llvm::raw_ostream &OS) const;
80130

81131
private:
82132
OriginID getNextOriginID() { return NextOriginID++; }
83133

134+
OriginTree *createNode(const ValueDecl *D) {
135+
OriginID NewID = getNextOriginID();
136+
AllOrigins.emplace_back(NewID, D);
137+
return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
138+
}
139+
140+
OriginTree *createNode(const Expr *E) {
141+
OriginID NewID = getNextOriginID();
142+
AllOrigins.emplace_back(NewID, E);
143+
return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
144+
}
145+
146+
template <typename T>
147+
OriginTree *buildTreeForType(QualType QT, const T *Node);
148+
149+
ASTContext& AST;
84150
OriginID NextOriginID{0};
85-
/// TODO(opt): Profile and evaluate the usefullness of small buffer
151+
/// TODO(opt): Profile and evaluate the usefulness of small buffer
86152
/// optimisation.
87153
llvm::SmallVector<Origin> AllOrigins;
88-
llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
89-
llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
154+
llvm::BumpPtrAllocator TreeAllocator;
155+
llvm::DenseMap<const clang::ValueDecl *, OriginTree *> DeclToTree;
156+
llvm::DenseMap<const clang::Expr *, OriginTree *> ExprToTree;
90157
};
91158
} // namespace clang::lifetimes::internal
92159

clang/lib/Analysis/LifetimeSafety/Facts.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
5353
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
5454
const OriginManager &OM) const {
5555
OS << "Use (";
56-
OM.dump(getUsedOrigin(), OS);
56+
for (OriginID OID : getUsedOrigins()) {
57+
OM.dump(OID, OS);
58+
OS << ", ";
59+
}
5760
OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
5861
}
5962

0 commit comments

Comments
 (0)