Skip to content

Commit 6f87445

Browse files
committed
[LifetimeSafety] Refactor the analysis into smaller files
1 parent 8a32e58 commit 6f87445

20 files changed

+1715
-1553
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//===- Checker.h - C++ Lifetime Safety Analysis -*----------- C++-*-=========//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// TODO: Complete me.
9+
//===----------------------------------------------------------------------===//
10+
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H
11+
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H
12+
13+
#include "clang/AST/Decl.h"
14+
#include "clang/AST/Expr.h"
15+
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
16+
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
17+
#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
18+
#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
19+
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
20+
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
21+
#include "clang/Analysis/AnalysisDeclContext.h"
22+
#include "clang/Analysis/CFG.h"
23+
#include "llvm/Support/ErrorHandling.h"
24+
#include "llvm/Support/TimeProfiler.h"
25+
26+
namespace clang::lifetimes {
27+
namespace internal {
28+
29+
// ========================================================================= //
30+
// Lifetime checker and Error reporter
31+
// ========================================================================= //
32+
33+
/// Struct to store the complete context for a potential lifetime violation.
34+
struct PendingWarning {
35+
SourceLocation ExpiryLoc; // Where the loan expired.
36+
const Expr *UseExpr; // Where the origin holding this loan was used.
37+
Confidence ConfidenceLevel;
38+
};
39+
40+
class LifetimeChecker {
41+
private:
42+
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
43+
LoanPropagationAnalysis &LoanPropagation;
44+
LiveOriginAnalysis &LiveOrigins;
45+
FactManager &FactMgr;
46+
AnalysisDeclContext &ADC;
47+
LifetimeSafetyReporter *Reporter;
48+
49+
public:
50+
LifetimeChecker(LoanPropagationAnalysis &LPA, LiveOriginAnalysis &LOA,
51+
FactManager &FM, AnalysisDeclContext &ADC,
52+
LifetimeSafetyReporter *Reporter)
53+
: LoanPropagation(LPA), LiveOrigins(LOA), FactMgr(FM), ADC(ADC),
54+
Reporter(Reporter) {}
55+
56+
void run() {
57+
llvm::TimeTraceScope TimeProfile("LifetimeChecker");
58+
for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
59+
for (const Fact *F : FactMgr.getFacts(B))
60+
if (const auto *EF = F->getAs<ExpireFact>())
61+
checkExpiry(EF);
62+
issuePendingWarnings();
63+
}
64+
65+
/// Checks for use-after-free errors when a loan expires.
66+
///
67+
/// This method examines all live origins at the expiry point and determines
68+
/// if any of them hold the expiring loan. If so, it creates a pending
69+
/// warning with the appropriate confidence level based on the liveness
70+
/// information. The confidence reflects whether the origin is definitely
71+
/// or maybe live at this point.
72+
///
73+
/// Note: This implementation considers only the confidence of origin
74+
/// liveness. Future enhancements could also consider the confidence of loan
75+
/// propagation (e.g., a loan may only be held on some execution paths).
76+
void checkExpiry(const ExpireFact *EF) {
77+
LoanID ExpiredLoan = EF->getLoanID();
78+
LivenessMap Origins = LiveOrigins.getLiveOrigins(EF);
79+
Confidence CurConfidence = Confidence::None;
80+
const UseFact *BadUse = nullptr;
81+
for (auto &[OID, LiveInfo] : Origins) {
82+
LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
83+
if (!HeldLoans.contains(ExpiredLoan))
84+
continue;
85+
// Loan is defaulted.
86+
Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
87+
if (CurConfidence < NewConfidence) {
88+
CurConfidence = NewConfidence;
89+
BadUse = LiveInfo.CausingUseFact;
90+
}
91+
}
92+
if (!BadUse)
93+
return;
94+
// We have a use-after-free.
95+
Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
96+
if (LastConf >= CurConfidence)
97+
return;
98+
FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
99+
/*UseExpr=*/BadUse->getUseExpr(),
100+
/*ConfidenceLevel=*/CurConfidence};
101+
}
102+
103+
static Confidence livenessKindToConfidence(LivenessKind K) {
104+
switch (K) {
105+
case LivenessKind::Must:
106+
return Confidence::Definite;
107+
case LivenessKind::Maybe:
108+
return Confidence::Maybe;
109+
case LivenessKind::Dead:
110+
return Confidence::None;
111+
}
112+
llvm_unreachable("unknown liveness kind");
113+
}
114+
115+
void issuePendingWarnings() {
116+
if (!Reporter)
117+
return;
118+
for (const auto &[LID, Warning] : FinalWarningsMap) {
119+
const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
120+
const Expr *IssueExpr = L.IssueExpr;
121+
Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr,
122+
Warning.ExpiryLoc, Warning.ConfidenceLevel);
123+
}
124+
}
125+
};
126+
} // namespace internal
127+
} // namespace clang::lifetimes
128+
129+
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
2+
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
3+
4+
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
5+
#include "clang/Analysis/AnalysisDeclContext.h"
6+
#include "clang/Analysis/CFG.h"
7+
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
8+
#include "llvm/Support/Debug.h"
9+
#include "llvm/Support/ErrorHandling.h"
10+
#include "llvm/Support/TimeProfiler.h"
11+
#include <optional>
12+
13+
namespace clang::lifetimes {
14+
namespace internal {
15+
16+
// ========================================================================= //
17+
// Generic Dataflow Analysis
18+
// ========================================================================= //
19+
20+
enum class Direction { Forward, Backward };
21+
22+
/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
23+
/// `Fact`. identified by a lifetime-related event (`Fact`).
24+
///
25+
/// A `ProgramPoint` has "after" semantics: it represents the location
26+
/// immediately after its corresponding `Fact`.
27+
using ProgramPoint = const Fact *;
28+
29+
/// A generic, policy-based driver for dataflow analyses. It combines
30+
/// the dataflow runner and the transferer logic into a single class hierarchy.
31+
///
32+
/// The derived class is expected to provide:
33+
/// - A `Lattice` type.
34+
/// - `StringRef getAnalysisName() const`
35+
/// - `Lattice getInitialState();` The initial state of the analysis.
36+
/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths.
37+
/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single
38+
/// lifetime-relevant `Fact` transforms the lattice state. Only overloads
39+
/// for facts relevant to the analysis need to be implemented.
40+
///
41+
/// \tparam Derived The CRTP derived class that implements the specific
42+
/// analysis.
43+
/// \tparam LatticeType The dataflow lattice used by the analysis.
44+
/// \tparam Dir The direction of the analysis (Forward or Backward).
45+
/// TODO: Maybe use the dataflow framework! The framework might need changes
46+
/// to support the current comparison done at block-entry.
47+
template <typename Derived, typename LatticeType, Direction Dir>
48+
class DataflowAnalysis {
49+
public:
50+
using Lattice = LatticeType;
51+
using Base = DataflowAnalysis<Derived, Lattice, Dir>;
52+
53+
private:
54+
const CFG &Cfg;
55+
AnalysisDeclContext &AC;
56+
57+
/// The dataflow state before a basic block is processed.
58+
llvm::DenseMap<const CFGBlock *, Lattice> InStates;
59+
/// The dataflow state after a basic block is processed.
60+
llvm::DenseMap<const CFGBlock *, Lattice> OutStates;
61+
/// The dataflow state at a Program Point.
62+
/// In a forward analysis, this is the state after the Fact at that point has
63+
/// been applied, while in a backward analysis, it is the state before.
64+
llvm::DenseMap<ProgramPoint, Lattice> PerPointStates;
65+
66+
static constexpr bool isForward() { return Dir == Direction::Forward; }
67+
68+
protected:
69+
FactManager &AllFacts;
70+
71+
explicit DataflowAnalysis(const CFG &C, AnalysisDeclContext &AC,
72+
FactManager &F)
73+
: Cfg(C), AC(AC), AllFacts(F) {}
74+
75+
public:
76+
void run() {
77+
Derived &D = static_cast<Derived &>(*this);
78+
llvm::TimeTraceScope Time(D.getAnalysisName());
79+
80+
using Worklist =
81+
std::conditional_t<Dir == Direction::Forward, ForwardDataflowWorklist,
82+
BackwardDataflowWorklist>;
83+
Worklist W(Cfg, AC);
84+
85+
const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit();
86+
InStates[Start] = D.getInitialState();
87+
W.enqueueBlock(Start);
88+
89+
while (const CFGBlock *B = W.dequeue()) {
90+
Lattice StateIn = *getInState(B);
91+
Lattice StateOut = transferBlock(B, StateIn);
92+
OutStates[B] = StateOut;
93+
for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) {
94+
if (!AdjacentB)
95+
continue;
96+
std::optional<Lattice> OldInState = getInState(AdjacentB);
97+
Lattice NewInState =
98+
!OldInState ? StateOut : D.join(*OldInState, StateOut);
99+
// Enqueue the adjacent block if its in-state has changed or if we have
100+
// never seen it.
101+
if (!OldInState || NewInState != *OldInState) {
102+
InStates[AdjacentB] = NewInState;
103+
W.enqueueBlock(AdjacentB);
104+
}
105+
}
106+
}
107+
}
108+
109+
protected:
110+
Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); }
111+
112+
std::optional<Lattice> getInState(const CFGBlock *B) const {
113+
auto It = InStates.find(B);
114+
if (It == InStates.end())
115+
return std::nullopt;
116+
return It->second;
117+
}
118+
119+
Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); }
120+
121+
void dump() const {
122+
const Derived *D = static_cast<const Derived *>(this);
123+
llvm::dbgs() << "==========================================\n";
124+
llvm::dbgs() << D->getAnalysisName() << " results:\n";
125+
llvm::dbgs() << "==========================================\n";
126+
const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry();
127+
getOutState(&B).dump(llvm::dbgs());
128+
}
129+
130+
private:
131+
/// Computes the state at one end of a block by applying all its facts
132+
/// sequentially to a given state from the other end.
133+
Lattice transferBlock(const CFGBlock *Block, Lattice State) {
134+
auto Facts = AllFacts.getFacts(Block);
135+
if constexpr (isForward()) {
136+
for (const Fact *F : Facts) {
137+
State = transferFact(State, F);
138+
PerPointStates[F] = State;
139+
}
140+
} else {
141+
for (const Fact *F : llvm::reverse(Facts)) {
142+
// In backward analysis, capture the state before applying the fact.
143+
PerPointStates[F] = State;
144+
State = transferFact(State, F);
145+
}
146+
}
147+
return State;
148+
}
149+
150+
Lattice transferFact(Lattice In, const Fact *F) {
151+
assert(F);
152+
Derived *D = static_cast<Derived *>(this);
153+
switch (F->getKind()) {
154+
case Fact::Kind::Issue:
155+
return D->transfer(In, *F->getAs<IssueFact>());
156+
case Fact::Kind::Expire:
157+
return D->transfer(In, *F->getAs<ExpireFact>());
158+
case Fact::Kind::OriginFlow:
159+
return D->transfer(In, *F->getAs<OriginFlowFact>());
160+
case Fact::Kind::ReturnOfOrigin:
161+
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
162+
case Fact::Kind::Use:
163+
return D->transfer(In, *F->getAs<UseFact>());
164+
case Fact::Kind::TestPoint:
165+
return D->transfer(In, *F->getAs<TestPointFact>());
166+
}
167+
llvm_unreachable("Unknown fact kind");
168+
}
169+
170+
public:
171+
Lattice transfer(Lattice In, const IssueFact &) { return In; }
172+
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
173+
Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
174+
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
175+
Lattice transfer(Lattice In, const UseFact &) { return In; }
176+
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
177+
};
178+
} // namespace internal
179+
} // namespace clang::lifetimes
180+
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H

0 commit comments

Comments
 (0)