Skip to content

Commit 15d7686

Browse files
kashika0112github-actions[bot]
authored andcommitted
Automerge: [LifetimeSafety] Detect use-after-return (#165370)
Adding "use-after-return" in Lifetime Analysis. Detecting when a function returns a reference to its own stack memory: [UAR Design Doc](https://docs.google.com/document/d/1Wxjn_rJD_tuRdejP81dlb9VOckTkCq5-aE1nGcerb_o/edit?usp=sharing) Consider the following example: ```cpp std::string_view foo() { std::string_view a; std::string str = "small scoped string"; a = str; return a; } ``` The code adds a new Fact "OriginEscape" in the end of the CFG to determine any loan that is escaping the function as shown below: ``` Function: foo Block B2: End of Block Block B1: OriginFlow (Dest: 0 (Decl: a), Src: 1 (Expr: CXXConstructExpr)) OriginFlow (Dest: 2 (Expr: ImplicitCastExpr), Src: 3 (Expr: StringLiteral)) Issue (0 (Path: operator=), ToOrigin: 4 (Expr: DeclRefExpr)) OriginFlow (Dest: 5 (Expr: ImplicitCastExpr), Src: 4 (Expr: DeclRefExpr)) Use (0 (Decl: a), Write) Issue (1 (Path: str), ToOrigin: 6 (Expr: DeclRefExpr)) OriginFlow (Dest: 7 (Expr: ImplicitCastExpr), Src: 6 (Expr: DeclRefExpr)) OriginFlow (Dest: 8 (Expr: CXXMemberCallExpr), Src: 7 (Expr: ImplicitCastExpr)) OriginFlow (Dest: 9 (Expr: ImplicitCastExpr), Src: 8 (Expr: CXXMemberCallExpr)) OriginFlow (Dest: 10 (Expr: ImplicitCastExpr), Src: 9 (Expr: ImplicitCastExpr)) OriginFlow (Dest: 11 (Expr: MaterializeTemporaryExpr), Src: 10 (Expr: ImplicitCastExpr)) OriginFlow (Dest: 0 (Decl: a), Src: 11 (Expr: MaterializeTemporaryExpr)) Use (0 (Decl: a), Read) OriginFlow (Dest: 12 (Expr: ImplicitCastExpr), Src: 0 (Decl: a)) OriginFlow (Dest: 13 (Expr: CXXConstructExpr), Src: 12 (Expr: ImplicitCastExpr)) Expire (1 (Path: str)) OriginEscapes (13 (Expr: CXXConstructExpr)) End of Block Block B0: End of Block ``` The confidence of the report is determined by checking if at least one of the loans returned is not expired (strict). If all loans are expired it is considered permissive. More information [UAR Design Doc](https://docs.google.com/document/d/1Wxjn_rJD_tuRdejP81dlb9VOckTkCq5-aE1nGcerb_o/edit?usp=sharing)
2 parents 530603e + 5343dd9 commit 15d7686

File tree

15 files changed

+567
-44
lines changed

15 files changed

+567
-44
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ class Fact {
4242
/// it. Otherwise, the source's loan set is merged into the destination's
4343
/// loan set.
4444
OriginFlow,
45-
/// An origin escapes the function by flowing into the return value.
46-
ReturnOfOrigin,
4745
/// An origin is used (eg. appears as l-value expression like DeclRefExpr).
4846
Use,
4947
/// A marker for a specific point in the code, for testing.
5048
TestPoint,
49+
/// An origin that escapes the function scope (e.g., via return).
50+
OriginEscapes,
5151
};
5252

5353
private:
@@ -136,16 +136,19 @@ class OriginFlowFact : public Fact {
136136
const OriginManager &OM) const override;
137137
};
138138

139-
class ReturnOfOriginFact : public Fact {
139+
class OriginEscapesFact : public Fact {
140140
OriginID OID;
141+
const Expr *EscapeExpr;
141142

142143
public:
143144
static bool classof(const Fact *F) {
144-
return F->getKind() == Kind::ReturnOfOrigin;
145+
return F->getKind() == Kind::OriginEscapes;
145146
}
146147

147-
ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {}
148-
OriginID getReturnedOriginID() const { return OID; }
148+
OriginEscapesFact(OriginID OID, const Expr *EscapeExpr)
149+
: Fact(Kind::OriginEscapes), OID(OID), EscapeExpr(EscapeExpr) {}
150+
OriginID getEscapedOriginID() const { return OID; }
151+
const Expr *getEscapeExpr() const { return EscapeExpr; };
149152
void dump(llvm::raw_ostream &OS, const LoanManager &,
150153
const OriginManager &OM) const override;
151154
};
@@ -225,6 +228,9 @@ class FactManager {
225228
/// user-defined locations in the code.
226229
/// \note This is intended for testing only.
227230
llvm::StringMap<ProgramPoint> getTestPoints() const;
231+
/// Retrieves all the facts in the block containing Program Point P.
232+
/// \note This is intended for testing only.
233+
llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) const;
228234

229235
unsigned getNumFacts() const { return NextFactID.Value; }
230236

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
9494
FactManager &FactMgr;
9595
AnalysisDeclContext &AC;
9696
llvm::SmallVector<Fact *> CurrentBlockFacts;
97+
// Collect origins that escape the function in this block (OriginEscapesFact),
98+
// appended at the end of CurrentBlockFacts to ensure they appear after
99+
// ExpireFact entries.
100+
llvm::SmallVector<Fact *> EscapesInCurrentBlock;
97101
// To distinguish between reads and writes for use-after-free checks, this map
98102
// stores the `UseFact` for each `DeclRefExpr`. We initially identify all
99103
// `DeclRefExpr`s as "read" uses. When an assignment is processed, the use

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class LifetimeSafetyReporter {
4242
virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
4343
SourceLocation FreeLoc,
4444
Confidence Confidence) {}
45+
46+
virtual void reportUseAfterReturn(const Expr *IssueExpr,
47+
const Expr *EscapeExpr,
48+
SourceLocation ExpiryLoc,
49+
Confidence Confidence) {}
4550
};
4651

4752
/// The main entry point for the analysis.

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131

3232
namespace clang::lifetimes::internal {
3333

34+
using CausingFactType =
35+
::llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>;
36+
3437
enum class LivenessKind : uint8_t {
3538
Dead, // Not alive
3639
Maybe, // Live on some path but not all paths (may-be-live)
@@ -43,7 +46,7 @@ struct LivenessInfo {
4346
/// multiple uses along different paths, this will point to the use appearing
4447
/// earlier in the translation unit.
4548
/// This is 'null' when the origin is not live.
46-
const UseFact *CausingUseFact;
49+
CausingFactType CausingFact;
4750

4851
/// The kind of liveness of the origin.
4952
/// `Must`: The origin is live on all control-flow paths from the current
@@ -56,17 +59,16 @@ struct LivenessInfo {
5659
/// while `Maybe`-be-alive suggests a potential one on some paths.
5760
LivenessKind Kind;
5861

59-
LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {}
60-
LivenessInfo(const UseFact *UF, LivenessKind K)
61-
: CausingUseFact(UF), Kind(K) {}
62+
LivenessInfo() : CausingFact(nullptr), Kind(LivenessKind::Dead) {}
63+
LivenessInfo(CausingFactType CF, LivenessKind K) : CausingFact(CF), Kind(K) {}
6264

6365
bool operator==(const LivenessInfo &Other) const {
64-
return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind;
66+
return CausingFact == Other.CausingFact && Kind == Other.Kind;
6567
}
6668
bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); }
6769

6870
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
69-
IDBuilder.AddPointer(CausingUseFact);
71+
IDBuilder.AddPointer(CausingFact.getOpaqueValue());
7072
IDBuilder.Add(Kind);
7173
}
7274
};

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10744,8 +10744,19 @@ def warn_lifetime_safety_loan_expires_permissive : Warning<
1074410744
def warn_lifetime_safety_loan_expires_strict : Warning<
1074510745
"object whose reference is captured may not live long enough">,
1074610746
InGroup<LifetimeSafetyStrict>, DefaultIgnore;
10747+
10748+
def warn_lifetime_safety_return_stack_addr_permissive
10749+
: Warning<"address of stack memory is returned later">,
10750+
InGroup<LifetimeSafetyPermissive>,
10751+
DefaultIgnore;
10752+
def warn_lifetime_safety_return_stack_addr_strict
10753+
: Warning<"address of stack memory may be returned later">,
10754+
InGroup<LifetimeSafetyStrict>,
10755+
DefaultIgnore;
10756+
1074710757
def note_lifetime_safety_used_here : Note<"later used here">;
1074810758
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
10759+
def note_lifetime_safety_returned_here : Note<"returned here">;
1074910760

1075010761
// For non-floating point, expressions of the form x == x or x != x
1075110762
// should result in a warning, since these always evaluate to a constant.

clang/lib/Analysis/LifetimeSafety/Checker.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ namespace {
4343
/// Struct to store the complete context for a potential lifetime violation.
4444
struct PendingWarning {
4545
SourceLocation ExpiryLoc; // Where the loan expired.
46-
const Expr *UseExpr; // Where the origin holding this loan was used.
46+
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
4747
Confidence ConfidenceLevel;
4848
};
4949

@@ -68,7 +68,7 @@ class LifetimeChecker {
6868
issuePendingWarnings();
6969
}
7070

71-
/// Checks for use-after-free errors when a loan expires.
71+
/// Checks for use-after-free & use-after-return errors when a loan expires.
7272
///
7373
/// This method examines all live origins at the expiry point and determines
7474
/// if any of them hold the expiring loan. If so, it creates a pending
@@ -83,7 +83,11 @@ class LifetimeChecker {
8383
LoanID ExpiredLoan = EF->getLoanID();
8484
LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
8585
Confidence CurConfidence = Confidence::None;
86-
const UseFact *BadUse = nullptr;
86+
// The UseFact or OriginEscapesFact most indicative of a lifetime error,
87+
// prioritized by earlier source location.
88+
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
89+
BestCausingFact = nullptr;
90+
8791
for (auto &[OID, LiveInfo] : Origins) {
8892
LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
8993
if (!HeldLoans.contains(ExpiredLoan))
@@ -92,17 +96,17 @@ class LifetimeChecker {
9296
Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
9397
if (CurConfidence < NewConfidence) {
9498
CurConfidence = NewConfidence;
95-
BadUse = LiveInfo.CausingUseFact;
99+
BestCausingFact = LiveInfo.CausingFact;
96100
}
97101
}
98-
if (!BadUse)
102+
if (!BestCausingFact)
99103
return;
100104
// We have a use-after-free.
101105
Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
102106
if (LastConf >= CurConfidence)
103107
return;
104108
FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
105-
/*UseExpr=*/BadUse->getUseExpr(),
109+
/*BestCausingFact=*/BestCausingFact,
106110
/*ConfidenceLevel=*/CurConfidence};
107111
}
108112

@@ -112,8 +116,20 @@ class LifetimeChecker {
112116
for (const auto &[LID, Warning] : FinalWarningsMap) {
113117
const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
114118
const Expr *IssueExpr = L.IssueExpr;
115-
Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr,
116-
Warning.ExpiryLoc, Warning.ConfidenceLevel);
119+
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
120+
CausingFact = Warning.CausingFact;
121+
Confidence Confidence = Warning.ConfidenceLevel;
122+
SourceLocation ExpiryLoc = Warning.ExpiryLoc;
123+
124+
if (const auto *UF = CausingFact.dyn_cast<const UseFact *>())
125+
Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
126+
Confidence);
127+
else if (const auto *OEF =
128+
CausingFact.dyn_cast<const OriginEscapesFact *>())
129+
Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
130+
ExpiryLoc, Confidence);
131+
else
132+
llvm_unreachable("Unhandled CausingFact type");
117133
}
118134
}
119135
};

clang/lib/Analysis/LifetimeSafety/Dataflow.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ class DataflowAnalysis {
170170
return D->transfer(In, *F->getAs<ExpireFact>());
171171
case Fact::Kind::OriginFlow:
172172
return D->transfer(In, *F->getAs<OriginFlowFact>());
173-
case Fact::Kind::ReturnOfOrigin:
174-
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
173+
case Fact::Kind::OriginEscapes:
174+
return D->transfer(In, *F->getAs<OriginEscapesFact>());
175175
case Fact::Kind::Use:
176176
return D->transfer(In, *F->getAs<UseFact>());
177177
case Fact::Kind::TestPoint:
@@ -184,7 +184,7 @@ class DataflowAnalysis {
184184
Lattice transfer(Lattice In, const IssueFact &) { return In; }
185185
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
186186
Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
187-
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
187+
Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; }
188188
Lattice transfer(Lattice In, const UseFact &) { return In; }
189189
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
190190
};

clang/lib/Analysis/LifetimeSafety/Facts.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
4343
OS << ")\n";
4444
}
4545

46-
void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
47-
const OriginManager &OM) const {
48-
OS << "ReturnOfOrigin (";
49-
OM.dump(getReturnedOriginID(), OS);
46+
void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
47+
const OriginManager &OM) const {
48+
OS << "OriginEscapes (";
49+
OM.dump(getEscapedOriginID(), OS);
5050
OS << ")\n";
5151
}
5252

@@ -95,4 +95,14 @@ void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
9595
}
9696
}
9797

98+
llvm::ArrayRef<const Fact *>
99+
FactManager::getBlockContaining(ProgramPoint P) const {
100+
for (const auto &BlockToFactsVec : BlockToFacts) {
101+
for (const Fact *F : BlockToFactsVec)
102+
if (F == P)
103+
return BlockToFactsVec;
104+
}
105+
return {};
106+
}
107+
98108
} // namespace clang::lifetimes::internal

clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ void FactsGenerator::run() {
5858
// initializations and destructions are processed in the correct sequence.
5959
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
6060
CurrentBlockFacts.clear();
61+
EscapesInCurrentBlock.clear();
6162
for (unsigned I = 0; I < Block->size(); ++I) {
6263
const CFGElement &Element = Block->Elements[I];
6364
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -66,6 +67,8 @@ void FactsGenerator::run() {
6667
Element.getAs<CFGAutomaticObjDtor>())
6768
handleDestructor(*DtorOpt);
6869
}
70+
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
71+
EscapesInCurrentBlock.end());
6972
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
7073
}
7174
}
@@ -166,7 +169,8 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
166169
if (const Expr *RetExpr = RS->getRetValue()) {
167170
if (hasOrigin(RetExpr)) {
168171
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
169-
CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID));
172+
EscapesInCurrentBlock.push_back(
173+
FactMgr.createFact<OriginEscapesFact>(OID, RetExpr));
170174
}
171175
}
172176
}

clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ struct Lattice {
5353
}
5454
};
5555

56+
static SourceLocation GetFactLoc(CausingFactType F) {
57+
if (const auto *UF = F.dyn_cast<const UseFact *>())
58+
return UF->getUseExpr()->getExprLoc();
59+
if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>())
60+
return OEF->getEscapeExpr()->getExprLoc();
61+
llvm_unreachable("unhandled causing fact in PointerUnion");
62+
}
63+
5664
/// The analysis that tracks which origins are live, with granular information
5765
/// about the causing use fact and confidence level. This is a backward
5866
/// analysis.
@@ -74,11 +82,14 @@ class AnalysisImpl
7482
/// one.
7583
Lattice join(Lattice L1, Lattice L2) const {
7684
LivenessMap Merged = L1.LiveOrigins;
77-
// Take the earliest UseFact to make the join hermetic and commutative.
78-
auto CombineUseFact = [](const UseFact &A,
79-
const UseFact &B) -> const UseFact * {
80-
return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A
81-
: &B;
85+
// Take the earliest Fact to make the join hermetic and commutative.
86+
auto CombineCausingFact = [](CausingFactType A,
87+
CausingFactType B) -> CausingFactType {
88+
if (!A)
89+
return B;
90+
if (!B)
91+
return A;
92+
return GetFactLoc(A) < GetFactLoc(B) ? A : B;
8293
};
8394
auto CombineLivenessKind = [](LivenessKind K1,
8495
LivenessKind K2) -> LivenessKind {
@@ -93,12 +104,11 @@ class AnalysisImpl
93104
const LivenessInfo *L2) -> LivenessInfo {
94105
assert((L1 || L2) && "unexpectedly merging 2 empty sets");
95106
if (!L1)
96-
return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe);
107+
return LivenessInfo(L2->CausingFact, LivenessKind::Maybe);
97108
if (!L2)
98-
return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe);
99-
return LivenessInfo(
100-
CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact),
101-
CombineLivenessKind(L1->Kind, L2->Kind));
109+
return LivenessInfo(L1->CausingFact, LivenessKind::Maybe);
110+
return LivenessInfo(CombineCausingFact(L1->CausingFact, L2->CausingFact),
111+
CombineLivenessKind(L1->Kind, L2->Kind));
102112
};
103113
return Lattice(utils::join(
104114
L1.LiveOrigins, L2.LiveOrigins, Factory, CombineLivenessInfo,
@@ -120,6 +130,14 @@ class AnalysisImpl
120130
LivenessInfo(&UF, LivenessKind::Must)));
121131
}
122132

133+
/// An escaping origin (e.g., via return) makes the origin live with definite
134+
/// confidence, as it dominates this program point.
135+
Lattice transfer(Lattice In, const OriginEscapesFact &OEF) {
136+
OriginID OID = OEF.getEscapedOriginID();
137+
return Lattice(Factory.add(In.LiveOrigins, OID,
138+
LivenessInfo(&OEF, LivenessKind::Must)));
139+
}
140+
123141
/// Issuing a new loan to an origin kills its liveness.
124142
Lattice transfer(Lattice In, const IssueFact &IF) {
125143
return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID()));

0 commit comments

Comments
 (0)