[clang] Adding use-after-return in Lifetime Analysis (PR #165370)
Kashika Akhouri via cfe-commits
cfe-commits at lists.llvm.org
Wed Nov 5 03:22:09 PST 2025
https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/165370
>From 2ab23aa98033c9f14cc6910786b6041c125b0726 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 1/2] Adding use-after-return in Lifetime Analysis
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 19 +
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 +
.../Analyses/LifetimeSafety/LifetimeSafety.h | 5 +
.../clang/Basic/DiagnosticSemaKinds.td | 1 +
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 77 +++-
clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 +
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 8 +
.../LifetimeSafety/FactsGenerator.cpp | 6 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 415 +++++++++---------
9 files changed, 329 insertions(+), 206 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 6a90aeb01e638..712e1d42e2966 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -44,6 +44,8 @@ class Fact {
Use,
/// A marker for a specific point in the code, for testing.
TestPoint,
+ OriginEscapes,
+ // An origin that stores a loan escapes the function.
};
private:
@@ -142,6 +144,23 @@ class ReturnOfOriginFact : public Fact {
const OriginManager &OM) const override;
};
+class OriginEscapesFact : public Fact {
+ OriginID OID;
+ const Stmt *EscapeSource;
+
+public:
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::OriginEscapes;
+ }
+
+ OriginEscapesFact(OriginID OID, const Stmt *Source)
+ : Fact(Kind::OriginEscapes), OID(OID), EscapeSource(Source) {}
+ OriginID getEscapedOriginID() const { return OID; }
+ const Stmt *getEscapeSource() const { return EscapeSource; }
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override;
+};
+
class UseFact : public Fact {
const Expr *UseExpr;
// True if this use is a write operation (e.g., left-hand side of assignment).
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 5e58abee2bbb3..ec3c130bb6c66 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -93,6 +93,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
FactManager &FactMgr;
AnalysisDeclContext &AC;
llvm::SmallVector<Fact *> CurrentBlockFacts;
+ llvm::SmallVector<Fact *> EscapesInCurrentBlock;
// To distinguish between reads and writes for use-after-free checks, this map
// stores the `UseFact` for each `DeclRefExpr`. We initially identify all
// `DeclRefExpr`s as "read" uses. When an assignment is processed, the use
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 91ffbb169f947..f5b1b3d9b6564 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -42,6 +42,11 @@ class LifetimeSafetyReporter {
virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
SourceLocation FreeLoc,
Confidence Confidence) {}
+
+ virtual void reportUseAfterReturn(const Expr *IssueExpr,
+ const Stmt *EscapeStmt,
+ SourceLocation ExpiryLoc,
+ Confidence Confidence) {}
};
/// The main entry point for the analysis.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5ff4cc4b833d9..6ea5e34cab291 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10730,6 +10730,7 @@ def warn_lifetime_safety_loan_expires_strict : Warning<
InGroup<LifetimeSafetyStrict>, DefaultIgnore;
def note_lifetime_safety_used_here : Note<"later used here">;
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
+def note_lifetime_safety_returned_here : Note<"returned 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/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index c443c3a5d4f9b..ae5c62ef04d72 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -21,6 +21,7 @@
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallSet.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TimeProfiler.h"
@@ -61,10 +62,23 @@ class LifetimeChecker {
AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
: LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
Reporter(Reporter) {
- for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
- for (const Fact *F : FactMgr.getFacts(B))
- if (const auto *EF = F->getAs<ExpireFact>())
+ for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) {
+ llvm::SmallVector<const ExpireFact *> BlockExpires;
+ llvm::SmallVector<const OriginEscapesFact *> BlockEscapes;
+ for (const Fact *F : FactMgr.getFacts(B)) {
+ if (const auto *EF = F->getAs<ExpireFact>()) {
checkExpiry(EF);
+ BlockExpires.push_back(EF);
+ } else if (const auto *OEF = F->getAs<OriginEscapesFact>()) {
+ BlockEscapes.push_back(OEF);
+ }
+ }
+ if (Reporter) {
+ for (const OriginEscapesFact *OEF : BlockEscapes) {
+ checkEscape(OEF, BlockExpires);
+ }
+ }
+ }
issuePendingWarnings();
}
@@ -106,6 +120,63 @@ class LifetimeChecker {
/*ConfidenceLevel=*/CurConfidence};
}
+ void checkEscape(const OriginEscapesFact *OEF,
+ llvm::ArrayRef<const ExpireFact *> BlockExpires) {
+
+ if (!Reporter) {
+ return;
+ }
+
+ OriginID returnedOID = OEF->getEscapedOriginID();
+ ProgramPoint PP = OEF;
+
+ LoanSet HeldLoans = LoanPropagation.getLoans(returnedOID, PP);
+ if (HeldLoans.isEmpty()) {
+ return;
+ }
+
+ llvm::SmallSet<LoanID, 4> ExpiredLoansInBlock;
+ llvm::DenseMap<LoanID, SourceLocation> ExpiryLocs;
+
+ for (const ExpireFact *EF : BlockExpires) {
+ ExpiredLoansInBlock.insert(EF->getLoanID());
+ ExpiryLocs[EF->getLoanID()] = EF->getExpiryLoc();
+ }
+
+ bool hasExpiredDependency = false;
+ bool allHeldLoansExpired = true;
+ LoanID exampleExpiredLoan = LoanID();
+
+ for (LoanID heldLoan : HeldLoans) {
+ if (ExpiredLoansInBlock.count(heldLoan)) {
+ hasExpiredDependency = true;
+ if (exampleExpiredLoan.Value == LoanID().Value) {
+ exampleExpiredLoan = heldLoan;
+ }
+ } else {
+ allHeldLoansExpired = false;
+ }
+ }
+
+ if (!hasExpiredDependency) {
+ return;
+ }
+
+ Confidence FinalConfidence;
+ if (allHeldLoansExpired) {
+ FinalConfidence = Confidence::Definite;
+ } else {
+ FinalConfidence = Confidence::Maybe;
+ }
+
+ const Loan &L = FactMgr.getLoanMgr().getLoan(exampleExpiredLoan);
+ SourceLocation ExpiryLoc = ExpiryLocs[exampleExpiredLoan];
+ const Stmt *EscapeSource = OEF->getEscapeSource();
+
+ Reporter->reportUseAfterReturn(L.IssueExpr, EscapeSource, ExpiryLoc,
+ FinalConfidence);
+ }
+
void issuePendingWarnings() {
if (!Reporter)
return;
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 2f7bcb6e5dc81..37720ffa03618 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -168,6 +168,8 @@ class DataflowAnalysis {
return D->transfer(In, *F->getAs<OriginFlowFact>());
case Fact::Kind::ReturnOfOrigin:
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
+ case Fact::Kind::OriginEscapes:
+ return D->transfer(In, *F->getAs<OriginEscapesFact>());
case Fact::Kind::Use:
return D->transfer(In, *F->getAs<UseFact>());
case Fact::Kind::TestPoint:
@@ -181,6 +183,7 @@ class DataflowAnalysis {
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
+ Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; }
Lattice transfer(Lattice In, const UseFact &) { return In; }
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
};
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 1aea64f864367..29c75959ba0fe 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -50,6 +50,14 @@ void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << ")\n";
}
+void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const {
+ OS << "OriginEscapes (";
+ OM.dump(getEscapedOriginID(), OS);
+ OS << ")\n";
+}
+
+
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const {
OS << "Use (";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9b68de107e314..762ed07eb5bb8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -58,6 +58,7 @@ void FactsGenerator::run() {
// initializations and destructions are processed in the correct sequence.
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
CurrentBlockFacts.clear();
+ EscapesInCurrentBlock.clear();
for (unsigned I = 0; I < Block->size(); ++I) {
const CFGElement &Element = Block->Elements[I];
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -66,6 +67,8 @@ void FactsGenerator::run() {
Element.getAs<CFGAutomaticObjDtor>())
handleDestructor(*DtorOpt);
}
+ CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
+ EscapesInCurrentBlock.end());
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
}
}
@@ -166,7 +169,8 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
if (const Expr *RetExpr = RS->getRetValue()) {
if (hasOrigin(RetExpr)) {
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
- CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID));
+ EscapesInCurrentBlock.push_back(
+ FactMgr.createFact<OriginEscapesFact>(OID, RS));
}
}
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 140b709dbb651..879793575e0b4 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -65,61 +65,61 @@ using namespace clang;
//===----------------------------------------------------------------------===//
namespace {
- class UnreachableCodeHandler : public reachable_code::Callback {
- Sema &S;
- SourceRange PreviousSilenceableCondVal;
-
- public:
- UnreachableCodeHandler(Sema &s) : S(s) {}
-
- void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
- SourceRange SilenceableCondVal, SourceRange R1,
- SourceRange R2, bool HasFallThroughAttr) override {
- // If the diagnosed code is `[[fallthrough]];` and
- // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never
- // be executed` warning to avoid generating diagnostic twice
- if (HasFallThroughAttr &&
- !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
- SourceLocation()))
- return;
+class UnreachableCodeHandler : public reachable_code::Callback {
+ Sema &S;
+ SourceRange PreviousSilenceableCondVal;
- // Avoid reporting multiple unreachable code diagnostics that are
- // triggered by the same conditional value.
+public:
+ UnreachableCodeHandler(Sema &s) : S(s) {}
+
+ void HandleUnreachable(reachable_code::UnreachableKind UK, SourceLocation L,
+ SourceRange SilenceableCondVal, SourceRange R1,
+ SourceRange R2, bool HasFallThroughAttr) override {
+ // If the diagnosed code is `[[fallthrough]];` and
+ // `-Wunreachable-code-fallthrough` is enabled, suppress `code will never
+ // be executed` warning to avoid generating diagnostic twice
+ if (HasFallThroughAttr &&
+ !S.getDiagnostics().isIgnored(diag::warn_unreachable_fallthrough_attr,
+ SourceLocation()))
+ return;
+
+ // Avoid reporting multiple unreachable code diagnostics that are
+ // triggered by the same conditional value.
if (PreviousSilenceableCondVal.isValid() &&
SilenceableCondVal.isValid() &&
- PreviousSilenceableCondVal == SilenceableCondVal)
- return;
- PreviousSilenceableCondVal = SilenceableCondVal;
+ PreviousSilenceableCondVal == SilenceableCondVal)
+ return;
+ PreviousSilenceableCondVal = SilenceableCondVal;
- unsigned diag = diag::warn_unreachable;
- switch (UK) {
- case reachable_code::UK_Break:
- diag = diag::warn_unreachable_break;
- break;
- case reachable_code::UK_Return:
- diag = diag::warn_unreachable_return;
- break;
- case reachable_code::UK_Loop_Increment:
- diag = diag::warn_unreachable_loop_increment;
- break;
- case reachable_code::UK_Other:
- break;
- }
+ unsigned diag = diag::warn_unreachable;
+ switch (UK) {
+ case reachable_code::UK_Break:
+ diag = diag::warn_unreachable_break;
+ break;
+ case reachable_code::UK_Return:
+ diag = diag::warn_unreachable_return;
+ break;
+ case reachable_code::UK_Loop_Increment:
+ diag = diag::warn_unreachable_loop_increment;
+ break;
+ case reachable_code::UK_Other:
+ break;
+ }
- S.Diag(L, diag) << R1 << R2;
+ S.Diag(L, diag) << R1 << R2;
- SourceLocation Open = SilenceableCondVal.getBegin();
- if (Open.isValid()) {
- SourceLocation Close = SilenceableCondVal.getEnd();
- Close = S.getLocForEndOfToken(Close);
- if (Close.isValid()) {
- S.Diag(Open, diag::note_unreachable_silence)
+ SourceLocation Open = SilenceableCondVal.getBegin();
+ if (Open.isValid()) {
+ SourceLocation Close = SilenceableCondVal.getEnd();
+ Close = S.getLocForEndOfToken(Close);
+ if (Close.isValid()) {
+ S.Diag(Open, diag::note_unreachable_silence)
<< FixItHint::CreateInsertion(Open, "/* DISABLES CODE */ (")
<< FixItHint::CreateInsertion(Close, ")");
- }
}
}
- };
+ }
+};
} // anonymous namespace
/// CheckUnreachable - Check for unreachable code.
@@ -388,9 +388,9 @@ static void checkThrowInNonThrowingFunc(Sema &S, const FunctionDecl *FD,
if (BodyCFG->getExit().pred_empty())
return;
visitReachableThrows(BodyCFG, [&](const CXXThrowExpr *Throw, CFGBlock &Block) {
- if (throwEscapes(S, Throw, Block, BodyCFG))
- EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD);
- });
+ if (throwEscapes(S, Throw, Block, BodyCFG))
+ EmitDiagForCXXThrowInNonThrowingFunc(S, Throw->getThrowLoc(), FD);
+ });
}
static bool isNoexcept(const FunctionDecl *FD) {
@@ -803,7 +803,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
}
else if (isa<BlockDecl>(D)) {
if (const FunctionType *FT =
- BlockType->getPointeeType()->getAs<FunctionType>()) {
+ BlockType->getPointeeType()->getAs<FunctionType>()) {
if (FT->getReturnType()->isVoidType())
ReturnsVoid = true;
if (FT->getNoReturnAttr())
@@ -815,7 +815,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
// Short circuit for compilation speed.
if (CD.checkDiagnostics(Diags, ReturnsVoid, HasNoReturn))
- return;
+ return;
SourceLocation LBrace = Body->getBeginLoc(), RBrace = Body->getEndLoc();
// cpu_dispatch functions permit empty function bodies for ICC compatibility.
@@ -892,7 +892,7 @@ class ContainsReference : public ConstEvaluatedExprVisitor<ContainsReference> {
typedef ConstEvaluatedExprVisitor<ContainsReference> Inherited;
ContainsReference(ASTContext &Context, const DeclRefExpr *Needle)
- : Inherited(Context), FoundReference(false), Needle(Needle) {}
+ : Inherited(Context), FoundReference(false), Needle(Needle) {}
void VisitExpr(const Expr *E) {
// Stop evaluating if we already have a reference.
@@ -1016,7 +1016,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
int RemoveDiagKind = -1;
const char *FixitStr =
S.getLangOpts().CPlusPlus ? (I->Output ? "true" : "false")
- : (I->Output ? "1" : "0");
+ : (I->Output ? "1" : "0");
FixItHint Fixit1, Fixit2;
switch (Term ? Term->getStmtClass() : Stmt::DeclStmtClass) {
@@ -1124,7 +1124,7 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
<< IsCapturedByBlock << User->getSourceRange();
if (RemoveDiagKind != -1)
S.Diag(Fixit1.RemoveRange.getBegin(), diag::note_uninit_fixit_remove_cond)
- << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2;
+ << RemoveDiagKind << Str << I->Output << Fixit1 << Fixit2;
Diagnosed = true;
}
@@ -1294,32 +1294,32 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor {
// Don't care about other unreachable statements.
}
}
- // If there are no unreachable statements, this may be a special
- // case in CFG:
- // case X: {
- // A a; // A has a destructor.
- // break;
- // }
- // // <<<< This place is represented by a 'hanging' CFG block.
- // case Y:
- continue;
+ // If there are no unreachable statements, this may be a special
+ // case in CFG:
+ // case X: {
+ // A a; // A has a destructor.
+ // break;
+ // }
+ // // <<<< This place is represented by a 'hanging' CFG block.
+ // case Y:
+ continue;
}
- const Stmt *LastStmt = getLastStmt(*P);
- if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) {
- markFallthroughVisited(AS);
- ++AnnotatedCnt;
- continue; // Fallthrough annotation, good.
- }
+ const Stmt *LastStmt = getLastStmt(*P);
+ if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) {
+ markFallthroughVisited(AS);
+ ++AnnotatedCnt;
+ continue; // Fallthrough annotation, good.
+ }
- if (!LastStmt) { // This block contains no executable statements.
- // Traverse its predecessors.
- std::copy(P->pred_begin(), P->pred_end(),
- std::back_inserter(BlockQueue));
- continue;
- }
+ if (!LastStmt) { // This block contains no executable statements.
+ // Traverse its predecessors.
+ std::copy(P->pred_begin(), P->pred_end(),
+ std::back_inserter(BlockQueue));
+ continue;
+ }
- ++UnannotatedCnt;
+ ++UnannotatedCnt;
}
return !!UnannotatedCnt;
}
@@ -1335,48 +1335,48 @@ class FallthroughMapper : public DynamicRecursiveASTVisitor {
return true;
}
- // We don't want to traverse local type declarations. We analyze their
- // methods separately.
- bool TraverseDecl(Decl *D) override { return true; }
+ // We don't want to traverse local type declarations. We analyze their
+ // methods separately.
+ bool TraverseDecl(Decl *D) override { return true; }
- // We analyze lambda bodies separately. Skip them here.
- bool TraverseLambdaExpr(LambdaExpr *LE) override {
- // Traverse the captures, but not the body.
- for (const auto C : zip(LE->captures(), LE->capture_inits()))
- TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C));
- return true;
- }
+ // We analyze lambda bodies separately. Skip them here.
+ bool TraverseLambdaExpr(LambdaExpr *LE) override {
+ // Traverse the captures, but not the body.
+ for (const auto C : zip(LE->captures(), LE->capture_inits()))
+ TraverseLambdaCapture(LE, &std::get<0>(C), std::get<1>(C));
+ return true;
+ }
- private:
+private:
- static const AttributedStmt *asFallThroughAttr(const Stmt *S) {
- if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) {
- if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs()))
- return AS;
- }
- return nullptr;
+ static const AttributedStmt *asFallThroughAttr(const Stmt *S) {
+ if (const AttributedStmt *AS = dyn_cast_or_null<AttributedStmt>(S)) {
+ if (hasSpecificAttr<FallThroughAttr>(AS->getAttrs()))
+ return AS;
}
+ return nullptr;
+ }
- static const Stmt *getLastStmt(const CFGBlock &B) {
- if (const Stmt *Term = B.getTerminatorStmt())
- return Term;
- for (const CFGElement &Elem : llvm::reverse(B))
- if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>())
- return CS->getStmt();
- // Workaround to detect a statement thrown out by CFGBuilder:
- // case X: {} case Y:
- // case X: ; case Y:
- if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel()))
- if (!isa<SwitchCase>(SW->getSubStmt()))
- return SW->getSubStmt();
+ static const Stmt *getLastStmt(const CFGBlock &B) {
+ if (const Stmt *Term = B.getTerminatorStmt())
+ return Term;
+ for (const CFGElement &Elem : llvm::reverse(B))
+ if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>())
+ return CS->getStmt();
+ // Workaround to detect a statement thrown out by CFGBuilder:
+ // case X: {} case Y:
+ // case X: ; case Y:
+ if (const SwitchCase *SW = dyn_cast_or_null<SwitchCase>(B.getLabel()))
+ if (!isa<SwitchCase>(SW->getSubStmt()))
+ return SW->getSubStmt();
- return nullptr;
- }
+ return nullptr;
+ }
- bool FoundSwitchStatements;
- AttrStmts FallthroughStmts;
- Sema &S;
- llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks;
+ bool FoundSwitchStatements;
+ AttrStmts FallthroughStmts;
+ Sema &S;
+ llvm::SmallPtrSet<const CFGBlock *, 16> ReachableBlocks;
};
} // anonymous namespace
@@ -1384,7 +1384,7 @@ static StringRef getFallthroughAttrSpelling(Preprocessor &PP,
SourceLocation Loc) {
TokenValue FallthroughTokens[] = {
tok::l_square, tok::l_square,
- PP.getIdentifierInfo("fallthrough"),
+ PP.getIdentifierInfo("fallthrough"),
tok::r_square, tok::r_square
};
@@ -1513,7 +1513,7 @@ static void diagnoseRepeatedUseOfWeak(Sema &S,
typedef sema::FunctionScopeInfo::WeakObjectUseMap WeakObjectUseMap;
typedef sema::FunctionScopeInfo::WeakUseVector WeakUseVector;
typedef std::pair<const Stmt *, WeakObjectUseMap::const_iterator>
- StmtUsesPair;
+ StmtUsesPair;
ASTContext &Ctx = S.getASTContext();
@@ -1977,7 +1977,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
: getNotes();
}
- public:
+public:
ThreadSafetyReporter(Sema &S, SourceLocation FL, SourceLocation FEL)
: S(S), FunLocation(FL), FunEndLocation(FEL),
CurrentFunction(nullptr), Verbose(false) {}
@@ -2075,18 +2075,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
bool ReentrancyMismatch) override {
unsigned DiagID = 0;
switch (LEK) {
- case LEK_LockedSomePredecessors:
- DiagID = diag::warn_lock_some_predecessors;
- break;
- case LEK_LockedSomeLoopIterations:
- DiagID = diag::warn_expecting_lock_held_on_loop;
- break;
- case LEK_LockedAtEndOfFunction:
- DiagID = diag::warn_no_unlock;
- break;
- case LEK_NotLockedAtEndOfFunction:
- DiagID = diag::warn_expecting_locked;
- break;
+ case LEK_LockedSomePredecessors:
+ DiagID = diag::warn_lock_some_predecessors;
+ break;
+ case LEK_LockedSomeLoopIterations:
+ DiagID = diag::warn_expecting_lock_held_on_loop;
+ break;
+ case LEK_LockedAtEndOfFunction:
+ DiagID = diag::warn_no_unlock;
+ break;
+ case LEK_NotLockedAtEndOfFunction:
+ DiagID = diag::warn_expecting_locked;
+ break;
}
if (LocEndOfScope.isInvalid())
LocEndOfScope = FunEndLocation;
@@ -2132,7 +2132,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
break;
}
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID)
- << D << getLockKindFromAccessKind(AK));
+ << D << getLockKindFromAccessKind(AK));
Warnings.emplace_back(std::move(Warning), getNotes());
}
@@ -2143,39 +2143,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
unsigned DiagID = 0;
if (PossibleMatch) {
switch (POK) {
- case POK_VarAccess:
- DiagID = diag::warn_variable_requires_lock_precise;
- break;
- case POK_VarDereference:
- DiagID = diag::warn_var_deref_requires_lock_precise;
- break;
- case POK_FunctionCall:
- DiagID = diag::warn_fun_requires_lock_precise;
- break;
- case POK_PassByRef:
- DiagID = diag::warn_guarded_pass_by_reference;
- break;
- case POK_PtPassByRef:
- DiagID = diag::warn_pt_guarded_pass_by_reference;
- break;
- case POK_ReturnByRef:
- DiagID = diag::warn_guarded_return_by_reference;
- break;
- case POK_PtReturnByRef:
- DiagID = diag::warn_pt_guarded_return_by_reference;
- break;
- case POK_PassPointer:
- DiagID = diag::warn_guarded_pass_pointer;
- break;
- case POK_PtPassPointer:
- DiagID = diag::warn_pt_guarded_pass_pointer;
- break;
- case POK_ReturnPointer:
- DiagID = diag::warn_guarded_return_pointer;
- break;
- case POK_PtReturnPointer:
- DiagID = diag::warn_pt_guarded_return_pointer;
- break;
+ case POK_VarAccess:
+ DiagID = diag::warn_variable_requires_lock_precise;
+ break;
+ case POK_VarDereference:
+ DiagID = diag::warn_var_deref_requires_lock_precise;
+ break;
+ case POK_FunctionCall:
+ DiagID = diag::warn_fun_requires_lock_precise;
+ break;
+ case POK_PassByRef:
+ DiagID = diag::warn_guarded_pass_by_reference;
+ break;
+ case POK_PtPassByRef:
+ DiagID = diag::warn_pt_guarded_pass_by_reference;
+ break;
+ case POK_ReturnByRef:
+ DiagID = diag::warn_guarded_return_by_reference;
+ break;
+ case POK_PtReturnByRef:
+ DiagID = diag::warn_pt_guarded_return_by_reference;
+ break;
+ case POK_PassPointer:
+ DiagID = diag::warn_guarded_pass_pointer;
+ break;
+ case POK_PtPassPointer:
+ DiagID = diag::warn_pt_guarded_pass_pointer;
+ break;
+ case POK_ReturnPointer:
+ DiagID = diag::warn_guarded_return_pointer;
+ break;
+ case POK_PtReturnPointer:
+ DiagID = diag::warn_pt_guarded_return_pointer;
+ break;
}
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
<< D
@@ -2191,39 +2191,39 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
Warnings.emplace_back(std::move(Warning), getNotes(Note));
} else {
switch (POK) {
- case POK_VarAccess:
- DiagID = diag::warn_variable_requires_lock;
- break;
- case POK_VarDereference:
- DiagID = diag::warn_var_deref_requires_lock;
- break;
- case POK_FunctionCall:
- DiagID = diag::warn_fun_requires_lock;
- break;
- case POK_PassByRef:
- DiagID = diag::warn_guarded_pass_by_reference;
- break;
- case POK_PtPassByRef:
- DiagID = diag::warn_pt_guarded_pass_by_reference;
- break;
- case POK_ReturnByRef:
- DiagID = diag::warn_guarded_return_by_reference;
- break;
- case POK_PtReturnByRef:
- DiagID = diag::warn_pt_guarded_return_by_reference;
- break;
- case POK_PassPointer:
- DiagID = diag::warn_guarded_pass_pointer;
- break;
- case POK_PtPassPointer:
- DiagID = diag::warn_pt_guarded_pass_pointer;
- break;
- case POK_ReturnPointer:
- DiagID = diag::warn_guarded_return_pointer;
- break;
- case POK_PtReturnPointer:
- DiagID = diag::warn_pt_guarded_return_pointer;
- break;
+ case POK_VarAccess:
+ DiagID = diag::warn_variable_requires_lock;
+ break;
+ case POK_VarDereference:
+ DiagID = diag::warn_var_deref_requires_lock;
+ break;
+ case POK_FunctionCall:
+ DiagID = diag::warn_fun_requires_lock;
+ break;
+ case POK_PassByRef:
+ DiagID = diag::warn_guarded_pass_by_reference;
+ break;
+ case POK_PtPassByRef:
+ DiagID = diag::warn_pt_guarded_pass_by_reference;
+ break;
+ case POK_ReturnByRef:
+ DiagID = diag::warn_guarded_return_by_reference;
+ break;
+ case POK_PtReturnByRef:
+ DiagID = diag::warn_pt_guarded_return_by_reference;
+ break;
+ case POK_PassPointer:
+ DiagID = diag::warn_guarded_pass_pointer;
+ break;
+ case POK_PtPassPointer:
+ DiagID = diag::warn_pt_guarded_pass_pointer;
+ break;
+ case POK_ReturnPointer:
+ DiagID = diag::warn_guarded_return_pointer;
+ break;
+ case POK_PtReturnPointer:
+ DiagID = diag::warn_pt_guarded_return_pointer;
+ break;
}
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
<< D
@@ -2241,7 +2241,7 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
SourceLocation Loc) override {
PartialDiagnosticAt Warning(Loc,
S.PDiag(diag::warn_acquire_requires_negative_cap)
- << Kind << LockName << Neg);
+ << Kind << LockName << Neg);
Warnings.emplace_back(std::move(Warning), getNotes());
}
@@ -2423,7 +2423,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
public:
UnsafeBufferUsageReporter(Sema &S, bool SuggestSuggestions)
- : S(S), SuggestSuggestions(SuggestSuggestions) {}
+ : S(S), SuggestSuggestions(SuggestSuggestions) {}
void handleUnsafeOperation(const Stmt *Operation, bool IsRelatedToDecl,
ASTContext &Ctx) override {
@@ -2808,6 +2808,17 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
<< UseExpr->getEndLoc();
}
+ void reportUseAfterReturn(const Expr *IssueExpr, const Stmt *EscapeStmt,
+ SourceLocation ExpiryLoc, Confidence C) override {
+ S.Diag(IssueExpr->getExprLoc(),
+ C == Confidence::Definite
+ ? diag::warn_lifetime_safety_loan_expires_permissive
+ : diag::warn_lifetime_safety_loan_expires_strict)
+ << IssueExpr->getEndLoc();
+
+ S.Diag(EscapeStmt->getBeginLoc(), diag::note_lifetime_safety_returned_here);
+ }
+
private:
Sema &S;
};
@@ -2815,7 +2826,7 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
} // namespace clang::lifetimes
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
- TranslationUnitDecl *TU) {
+ TranslationUnitDecl *TU) {
if (!TU)
return; // This is unexpected, give up quietly.
@@ -2928,13 +2939,13 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
AC.getCFGBuildOptions().setAllAlwaysAdd();
} else {
AC.getCFGBuildOptions()
- .setAlwaysAdd(Stmt::BinaryOperatorClass)
- .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
- .setAlwaysAdd(Stmt::BlockExprClass)
- .setAlwaysAdd(Stmt::CStyleCastExprClass)
- .setAlwaysAdd(Stmt::DeclRefExprClass)
- .setAlwaysAdd(Stmt::ImplicitCastExprClass)
- .setAlwaysAdd(Stmt::UnaryOperatorClass);
+ .setAlwaysAdd(Stmt::BinaryOperatorClass)
+ .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
+ .setAlwaysAdd(Stmt::BlockExprClass)
+ .setAlwaysAdd(Stmt::CStyleCastExprClass)
+ .setAlwaysAdd(Stmt::DeclRefExprClass)
+ .setAlwaysAdd(Stmt::ImplicitCastExprClass)
+ .setAlwaysAdd(Stmt::UnaryOperatorClass);
}
// Install the logical handler.
>From 0ba83e2a940795f3427f7141c20822853c51e63a Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 28 Oct 2025 10:59:48 +0000
Subject: [PATCH 2/2] Adding use-after-return in Lifetime Analysis
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 26 +-
.../Analyses/LifetimeSafety/FactsGenerator.h | 2 +
.../Analyses/LifetimeSafety/LifetimeSafety.h | 2 +-
.../Analyses/LifetimeSafety/LiveOrigins.h | 11 +-
.../clang/Basic/DiagnosticSemaKinds.td | 10 +
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 91 ++-----
clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 -
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 7 -
.../LifetimeSafety/FactsGenerator.cpp | 5 +-
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 48 +++-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 8 +-
.../Sema/warn-lifetime-safety-dataflow.cpp | 4 +-
clang/test/Sema/warn-lifetime-safety.cpp | 118 +++++++++
.../unittests/Analysis/LifetimeSafetyTest.cpp | 237 ++++++++++++++++++
14 files changed, 446 insertions(+), 126 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 712e1d42e2966..1b87eee4a8d17 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -39,13 +39,11 @@ class Fact {
/// loan set.
OriginFlow,
/// An origin escapes the function by flowing into the return value.
- ReturnOfOrigin,
- /// An origin is used (eg. appears as l-value expression like DeclRefExpr).
Use,
/// A marker for a specific point in the code, for testing.
TestPoint,
+ // Indicates that an origin escapes the function scope (e.g., via return).
OriginEscapes,
- // An origin that stores a loan escapes the function.
};
private:
@@ -130,33 +128,19 @@ class OriginFlowFact : public Fact {
const OriginManager &OM) const override;
};
-class ReturnOfOriginFact : public Fact {
- OriginID OID;
-
-public:
- static bool classof(const Fact *F) {
- return F->getKind() == Kind::ReturnOfOrigin;
- }
-
- ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {}
- OriginID getReturnedOriginID() const { return OID; }
- void dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &OM) const override;
-};
-
class OriginEscapesFact : public Fact {
OriginID OID;
- const Stmt *EscapeSource;
+ const Expr *EscapeExpr;
public:
static bool classof(const Fact *F) {
return F->getKind() == Kind::OriginEscapes;
}
- OriginEscapesFact(OriginID OID, const Stmt *Source)
- : Fact(Kind::OriginEscapes), OID(OID), EscapeSource(Source) {}
+ OriginEscapesFact(OriginID OID, const Expr *EscapeExpr)
+ : Fact(Kind::OriginEscapes), OID(OID), EscapeExpr(EscapeExpr) {}
OriginID getEscapedOriginID() const { return OID; }
- const Stmt *getEscapeSource() const { return EscapeSource; }
+ const Expr *getEscapeExpr() const { return EscapeExpr; };
void dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const override;
};
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index ec3c130bb6c66..21d160493289e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -93,6 +93,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
FactManager &FactMgr;
AnalysisDeclContext &AC;
llvm::SmallVector<Fact *> CurrentBlockFacts;
+ // Collect origins that escape the function in this block. These are handled
+ // at the end of the block to ensure `OEFs` appear after `Expire` facts.
llvm::SmallVector<Fact *> EscapesInCurrentBlock;
// To distinguish between reads and writes for use-after-free checks, this map
// stores the `UseFact` for each `DeclRefExpr`. We initially identify all
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index f5b1b3d9b6564..b34a7f18b5809 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -44,7 +44,7 @@ class LifetimeSafetyReporter {
Confidence Confidence) {}
virtual void reportUseAfterReturn(const Expr *IssueExpr,
- const Stmt *EscapeStmt,
+ const Expr *EscapeExpr,
SourceLocation ExpiryLoc,
Confidence Confidence) {}
};
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
index c4f5f0e9ae46c..dae97501c0b53 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h
@@ -43,7 +43,7 @@ struct LivenessInfo {
/// multiple uses along different paths, this will point to the use appearing
/// earlier in the translation unit.
/// This is 'null' when the origin is not live.
- const UseFact *CausingUseFact;
+ const Fact *CausingFact;
/// The kind of liveness of the origin.
/// `Must`: The origin is live on all control-flow paths from the current
@@ -56,17 +56,16 @@ struct LivenessInfo {
/// while `Maybe`-be-alive suggests a potential one on some paths.
LivenessKind Kind;
- LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {}
- LivenessInfo(const UseFact *UF, LivenessKind K)
- : CausingUseFact(UF), Kind(K) {}
+ LivenessInfo() : CausingFact(nullptr), Kind(LivenessKind::Dead) {}
+ LivenessInfo(const Fact *CF, LivenessKind K) : CausingFact(CF), Kind(K) {}
bool operator==(const LivenessInfo &Other) const {
- return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind;
+ return CausingFact == Other.CausingFact && Kind == Other.Kind;
}
bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); }
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
- IDBuilder.AddPointer(CausingUseFact);
+ IDBuilder.AddPointer(CausingFact);
IDBuilder.Add(Kind);
}
};
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6ea5e34cab291..4eb7263715f4a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10728,6 +10728,16 @@ def warn_lifetime_safety_loan_expires_permissive : Warning<
def warn_lifetime_safety_loan_expires_strict : Warning<
"object whose reference is captured may not live long enough">,
InGroup<LifetimeSafetyStrict>, DefaultIgnore;
+
+def warn_lifetime_safety_return_stack_addr_permissive
+ : Warning<"returning reference to stack allocated object">,
+ InGroup<LifetimeSafetyPermissive>,
+ DefaultIgnore;
+def warn_lifetime_safety_return_stack_addr_strict
+ : Warning<"may be returning reference to stack allocated object">,
+ InGroup<LifetimeSafetyStrict>,
+ DefaultIgnore;
+
def note_lifetime_safety_used_here : Note<"later used here">;
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
def note_lifetime_safety_returned_here : Note<"returned here">;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index ae5c62ef04d72..64ca5439cc264 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -44,7 +44,7 @@ namespace {
/// Struct to store the complete context for a potential lifetime violation.
struct PendingWarning {
SourceLocation ExpiryLoc; // Where the loan expired.
- const Expr *UseExpr; // Where the origin holding this loan was used.
+ const Fact *CausingFact; // If the use is a UseFact or OriginEscapeFact
Confidence ConfidenceLevel;
};
@@ -64,25 +64,17 @@ class LifetimeChecker {
Reporter(Reporter) {
for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) {
llvm::SmallVector<const ExpireFact *> BlockExpires;
- llvm::SmallVector<const OriginEscapesFact *> BlockEscapes;
for (const Fact *F : FactMgr.getFacts(B)) {
if (const auto *EF = F->getAs<ExpireFact>()) {
checkExpiry(EF);
BlockExpires.push_back(EF);
- } else if (const auto *OEF = F->getAs<OriginEscapesFact>()) {
- BlockEscapes.push_back(OEF);
- }
- }
- if (Reporter) {
- for (const OriginEscapesFact *OEF : BlockEscapes) {
- checkEscape(OEF, BlockExpires);
}
}
}
issuePendingWarnings();
}
- /// Checks for use-after-free errors when a loan expires.
+ /// Checks for use-after-free & use-after-return errors when a loan expires.
///
/// This method examines all live origins at the expiry point and determines
/// if any of them hold the expiring loan. If so, it creates a pending
@@ -97,7 +89,8 @@ class LifetimeChecker {
LoanID ExpiredLoan = EF->getLoanID();
LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
Confidence CurConfidence = Confidence::None;
- const UseFact *BadUse = nullptr;
+ const Fact *BestCausingFact = nullptr;
+
for (auto &[OID, LiveInfo] : Origins) {
LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
if (!HeldLoans.contains(ExpiredLoan))
@@ -106,85 +99,37 @@ class LifetimeChecker {
Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
if (CurConfidence < NewConfidence) {
CurConfidence = NewConfidence;
- BadUse = LiveInfo.CausingUseFact;
+ BestCausingFact = LiveInfo.CausingFact;
}
}
- if (!BadUse)
+ if (!BestCausingFact)
return;
// We have a use-after-free.
Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
if (LastConf >= CurConfidence)
return;
FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
- /*UseExpr=*/BadUse->getUseExpr(),
+ /*BestCausingFact*/ BestCausingFact,
/*ConfidenceLevel=*/CurConfidence};
}
- void checkEscape(const OriginEscapesFact *OEF,
- llvm::ArrayRef<const ExpireFact *> BlockExpires) {
-
- if (!Reporter) {
- return;
- }
-
- OriginID returnedOID = OEF->getEscapedOriginID();
- ProgramPoint PP = OEF;
-
- LoanSet HeldLoans = LoanPropagation.getLoans(returnedOID, PP);
- if (HeldLoans.isEmpty()) {
- return;
- }
-
- llvm::SmallSet<LoanID, 4> ExpiredLoansInBlock;
- llvm::DenseMap<LoanID, SourceLocation> ExpiryLocs;
-
- for (const ExpireFact *EF : BlockExpires) {
- ExpiredLoansInBlock.insert(EF->getLoanID());
- ExpiryLocs[EF->getLoanID()] = EF->getExpiryLoc();
- }
-
- bool hasExpiredDependency = false;
- bool allHeldLoansExpired = true;
- LoanID exampleExpiredLoan = LoanID();
-
- for (LoanID heldLoan : HeldLoans) {
- if (ExpiredLoansInBlock.count(heldLoan)) {
- hasExpiredDependency = true;
- if (exampleExpiredLoan.Value == LoanID().Value) {
- exampleExpiredLoan = heldLoan;
- }
- } else {
- allHeldLoansExpired = false;
- }
- }
-
- if (!hasExpiredDependency) {
- return;
- }
-
- Confidence FinalConfidence;
- if (allHeldLoansExpired) {
- FinalConfidence = Confidence::Definite;
- } else {
- FinalConfidence = Confidence::Maybe;
- }
-
- const Loan &L = FactMgr.getLoanMgr().getLoan(exampleExpiredLoan);
- SourceLocation ExpiryLoc = ExpiryLocs[exampleExpiredLoan];
- const Stmt *EscapeSource = OEF->getEscapeSource();
-
- Reporter->reportUseAfterReturn(L.IssueExpr, EscapeSource, ExpiryLoc,
- FinalConfidence);
- }
-
void issuePendingWarnings() {
if (!Reporter)
return;
for (const auto &[LID, Warning] : FinalWarningsMap) {
const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
const Expr *IssueExpr = L.IssueExpr;
- Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr,
- Warning.ExpiryLoc, Warning.ConfidenceLevel);
+ const Fact *CausingFact = Warning.CausingFact;
+ Confidence Confidence = Warning.ConfidenceLevel;
+ SourceLocation ExpiryLoc = Warning.ExpiryLoc;
+
+ if (const auto *UF = CausingFact->getAs<UseFact>()) {
+ Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
+ Confidence);
+ } else if (const auto *OEF = CausingFact->getAs<OriginEscapesFact>()) {
+ Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
+ ExpiryLoc, Confidence);
+ }
}
}
};
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 37720ffa03618..23ef9dbcdcb86 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -166,8 +166,6 @@ class DataflowAnalysis {
return D->transfer(In, *F->getAs<ExpireFact>());
case Fact::Kind::OriginFlow:
return D->transfer(In, *F->getAs<OriginFlowFact>());
- case Fact::Kind::ReturnOfOrigin:
- return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
case Fact::Kind::OriginEscapes:
return D->transfer(In, *F->getAs<OriginEscapesFact>());
case Fact::Kind::Use:
@@ -182,7 +180,6 @@ class DataflowAnalysis {
Lattice transfer(Lattice In, const IssueFact &) { return In; }
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
- Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; }
Lattice transfer(Lattice In, const UseFact &) { return In; }
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 29c75959ba0fe..45fce4bcf8ba3 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -43,13 +43,6 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << ")\n";
}
-void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
- const OriginManager &OM) const {
- OS << "ReturnOfOrigin (";
- OM.dump(getReturnedOriginID(), OS);
- OS << ")\n";
-}
-
void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const {
OS << "OriginEscapes (";
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 762ed07eb5bb8..72a1b52786d54 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -59,6 +59,7 @@ void FactsGenerator::run() {
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
CurrentBlockFacts.clear();
EscapesInCurrentBlock.clear();
+ EscapesInCurrentBlock.clear();
for (unsigned I = 0; I < Block->size(); ++I) {
const CFGElement &Element = Block->Elements[I];
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
@@ -67,6 +68,8 @@ void FactsGenerator::run() {
Element.getAs<CFGAutomaticObjDtor>())
handleDestructor(*DtorOpt);
}
+ CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
+ EscapesInCurrentBlock.end());
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
EscapesInCurrentBlock.end());
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
@@ -170,7 +173,7 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
if (hasOrigin(RetExpr)) {
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
EscapesInCurrentBlock.push_back(
- FactMgr.createFact<OriginEscapesFact>(OID, RS));
+ FactMgr.createFact<OriginEscapesFact>(OID, RetExpr));
}
}
}
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index cddb3f3ce4c1c..02a079184723c 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -53,6 +53,16 @@ struct Lattice {
}
};
+static SourceLocation GetFactLoc(const Fact &F) {
+ if (const auto *UF = F.getAs<UseFact>()) {
+ return UF->getUseExpr()->getExprLoc();
+ }
+ if (const auto *OEF = F.getAs<OriginEscapesFact>()) {
+ return OEF->getEscapeExpr()->getExprLoc();
+ }
+ return SourceLocation(); // Invalid SourceLocation
+}
+
/// The analysis that tracks which origins are live, with granular information
/// about the causing use fact and confidence level. This is a backward
/// analysis.
@@ -74,11 +84,25 @@ class AnalysisImpl
/// one.
Lattice join(Lattice L1, Lattice L2) const {
LivenessMap Merged = L1.LiveOrigins;
- // Take the earliest UseFact to make the join hermetic and commutative.
- auto CombineUseFact = [](const UseFact &A,
- const UseFact &B) -> const UseFact * {
- return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A
- : &B;
+ // Take the earliest Fact to make the join hermetic and commutative.
+ auto CombineCausingFact = [](const Fact &A, const Fact &B) -> const Fact * {
+ SourceLocation LocA = GetFactLoc(A);
+ SourceLocation LocB = GetFactLoc(B);
+
+ bool aValid = LocA.isValid();
+ bool bValid = LocB.isValid();
+
+ if (aValid && bValid) {
+ if (LocA < LocB)
+ return &A;
+ if (LocB < LocA)
+ return &B;
+ } else if (aValid) {
+ return &A;
+ } else if (bValid) {
+ return &B;
+ }
+ return &A < &B ? &A : &B;
};
auto CombineLivenessKind = [](LivenessKind K1,
LivenessKind K2) -> LivenessKind {
@@ -93,11 +117,11 @@ class AnalysisImpl
const LivenessInfo *L2) -> LivenessInfo {
assert((L1 || L2) && "unexpectedly merging 2 empty sets");
if (!L1)
- return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe);
+ return LivenessInfo(L2->CausingFact, LivenessKind::Maybe);
if (!L2)
- return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe);
+ return LivenessInfo(L1->CausingFact, LivenessKind::Maybe);
return LivenessInfo(
- CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact),
+ CombineCausingFact(*L1->CausingFact, *L2->CausingFact),
CombineLivenessKind(L1->Kind, L2->Kind));
};
return Lattice(utils::join(
@@ -120,6 +144,14 @@ class AnalysisImpl
LivenessInfo(&UF, LivenessKind::Must)));
}
+ // A return operation makes the origin live with definite confidence, as it
+ /// dominates this program point.
+ Lattice transfer(Lattice In, const OriginEscapesFact &OEF) {
+ OriginID OID = OEF.getEscapedOriginID();
+ return Lattice(Factory.add(In.LiveOrigins, OID,
+ LivenessInfo(&OEF, LivenessKind::Must)));
+ }
+
/// Issuing a new loan to an origin kills its liveness.
Lattice transfer(Lattice In, const IssueFact &IF) {
return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID()));
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 879793575e0b4..08b621f39441c 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2808,15 +2808,15 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
<< UseExpr->getEndLoc();
}
- void reportUseAfterReturn(const Expr *IssueExpr, const Stmt *EscapeStmt,
+ void reportUseAfterReturn(const Expr *IssueExpr, const Expr *EscapeExpr,
SourceLocation ExpiryLoc, Confidence C) override {
S.Diag(IssueExpr->getExprLoc(),
C == Confidence::Definite
- ? diag::warn_lifetime_safety_loan_expires_permissive
- : diag::warn_lifetime_safety_loan_expires_strict)
+ ? diag::warn_lifetime_safety_return_stack_addr_permissive
+ : diag::warn_lifetime_safety_return_stack_addr_strict)
<< IssueExpr->getEndLoc();
- S.Diag(EscapeStmt->getBeginLoc(), diag::note_lifetime_safety_returned_here);
+ S.Diag(EscapeExpr->getBeginLoc(), diag::note_lifetime_safety_returned_here);
}
private:
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 31148b990d6bd..32796c2ae43c4 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -18,8 +18,8 @@ MyObj* return_local_addr() {
return p;
// CHECK: Use ([[O_P]] (Decl: p), Read)
// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_P]] (Decl: p))
-// CHECK: ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
// CHECK: Expire ([[L_X]] (Path: x))
+// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
}
@@ -49,8 +49,8 @@ MyObj* assign_and_return_local_addr() {
return ptr2;
// CHECK: Use ([[O_PTR2]] (Decl: ptr2), Read)
// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR2]] (Decl: ptr2))
-// CHECK: ReturnOfOrigin ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
// CHECK: Expire ([[L_Y]] (Path: y))
+// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
}
// Return of Non-Pointer Type
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 4f234f0ac6e2d..55bf6253a8c82 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -396,6 +396,118 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
} // expected-note {{destroyed here}}
}
+//===----------------------------------------------------------------------===//
+// Basic Definite Use-After-Return (Return-Stack-Address) (-W...permissive)
+// These are cases where the pointer is guaranteed to be dangling at the use site.
+//===----------------------------------------------------------------------===//
+
+MyObj* simple_return_stack_address(){
+ MyObj s;
+ MyObj* p = &s; // expected-warning {{returning reference to stack allocated object}}
+ return p; // expected-note {{returned here}}
+}
+
+MyObj* conditional_assign_unconditional_return(MyObj safe, bool c){
+ MyObj s;
+ MyObj* p = &safe;
+ if(c){
+ p = &s; // expected-warning {{returning reference to stack allocated object}}
+ }
+ return p; // expected-note {{returned here}}
+}
+
+MyObj* conditional_assign_both_branches(MyObj safe, bool c){
+
+ MyObj s;
+ MyObj* p = nullptr;
+ if (c) {
+ p = &s; // expected-warning {{returning reference to stack allocated object}}
+ } else {
+ p = &safe;
+ }
+ return p; // expected-note {{returned here}}
+
+}
+
+MyObj* reassign_safe_to_local(MyObj safe){
+ MyObj local;
+ MyObj* p = &safe;
+
+ p = &local; // expected-warning {{returning reference to stack allocated object}}
+ return p; // expected-note {{returned here}}
+}
+
+MyObj* pointer_chain_to_local(){
+ MyObj local;
+ MyObj* p1 = &local; // expected-warning {{returning reference to stack allocated object}}
+
+ MyObj* p2 = p1;
+
+ return p2; // expected-note {{returned here}}
+}
+
+MyObj* multiple_assign_multiple_return(MyObj safe, bool c1, bool c2){
+ MyObj local1;
+ MyObj local2;
+ MyObj* p = nullptr;
+ if(c1){
+ p = &local1; // expected-warning {{returning reference to stack allocated object}}
+ return p; // expected-note {{returned here}}
+ }
+ else if(c2){
+ p = &local2; // expected-warning {{returning reference to stack allocated object}}
+ return p; // expected-note {{returned here}}
+ }
+ p = &safe;
+ return p;
+}
+
+MyObj* multiple_assign_single_return(MyObj safe, bool c1, bool c2){
+ MyObj local1;
+ MyObj local2;
+ MyObj* p = nullptr;
+ if(c1){
+ p = &local1; // expected-warning {{returning reference to stack allocated object}}
+ }
+ else if(c2){
+ p = &local2; // expected-warning {{returning reference to stack allocated object}}
+ }
+ else{
+ p = &safe;
+ }
+
+ return p; // expected-note {{returned here}} // expected-note {{returned here}}
+}
+
+//===----------------------------------------------------------------------===//
+// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
+// These are cases where the diagnostic kind is determined by location
+//===----------------------------------------------------------------------===//
+
+MyObj* uaf_before_uar(){
+ MyObj* p;
+ {
+ MyObj local_obj;
+ p = &local_obj; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ return p; // expected-note {{later used here}}
+}
+
+MyObj* uar_before_uaf(MyObj safe, bool c){
+ MyObj* p;
+ {
+ MyObj local_obj;
+ p = &local_obj; // expected-warning {{returning reference to stack allocated object}}
+ if(c){
+ return p; // expected-note {{returned here}}
+ }
+
+ }
+ (void)*p;
+ p = &safe;
+ return p;
+}
+
//===----------------------------------------------------------------------===//
// No-Error Cases
//===----------------------------------------------------------------------===//
@@ -434,6 +546,12 @@ void no_error_loan_from_current_iteration(bool cond) {
}
}
+MyObj* safe_return(MyObj safe){
+ MyObj local;
+ MyObj *p = &local;
+ p = &safe; // p has been reassigned
+ return p; // This is safe
+}
//===----------------------------------------------------------------------===//
// Lifetimebound Attribute Tests
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 0c051847f4d47..5370cde779a8a 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1202,5 +1202,242 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1"));
}
+TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) {
+ SetupTest(R"(
+ MyObj* target() {
+ MyObj s;
+ MyObj* p = &s;
+ POINT(p1);
+ return p;
+ }
+ )");
+ EXPECT_THAT(Origin("p"), HasLoansTo({"s"}, "p1"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) {
+ SetupTest(R"(
+ MyObj* target(bool c) {
+ MyObj s1;
+ MyObj* p = nullptr;
+ POINT(before_if);
+ if (c) {
+ p = &s1;
+ POINT(after_assign);
+ }
+ POINT(after_if);
+ return p;
+ }
+ )");
+ EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_if"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if"));
+ EXPECT_THAT(Origins({"p"}), MaybeLiveAt("before_if"));
+}
+
+TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) {
+ SetupTest(R"(
+ MyObj* target(bool c) {
+ MyObj s1;
+ static MyObj s2;
+ MyObj* p = nullptr;
+ POINT(before_if);
+ if (c) {
+ p = &s1;
+ POINT(after_assign_if);
+ } else {
+ p = &s2;
+ POINT(after_assign_else);
+ }
+ POINT(after_if);
+ return p;
+ }
+ )");
+ EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_if"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_if"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_assign_if"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_if"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_assign_else"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_assign_else"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_if"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("before_if"));
+}
+
+TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) {
+ SetupTest(R"(
+ MyObj* target() {
+ static MyObj safe_obj;
+ MyObj local_obj;
+ MyObj* p = &safe_obj;
+ POINT(p1);
+
+ p = &local_obj;
+ POINT(p2);
+ return p;
+ }
+ )");
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({"safe_obj"}, "p1"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, PointerChainToLocal) {
+ SetupTest(R"(
+ MyObj* target() {
+ MyObj local_obj;
+ MyObj* p1 = &local_obj;
+ POINT(p_p1);
+ MyObj* p2 = p1;
+ POINT(p_p2);
+ return p2;
+ }
+ )");
+ EXPECT_THAT(Origin("p2"), HasLoansTo({"local_obj"}, "p_p2"));
+ EXPECT_THAT(Origins({"p2"}), MustBeLiveAt("p_p2"));
+ EXPECT_THAT(Origins({"p1"}), testing::Not(AreLiveAt("p_p2")));
+
+ EXPECT_THAT(Origin("p1"), HasLoansTo({"local_obj"}, "p_p1"));
+ EXPECT_THAT(Origins({"p1"}), MustBeLiveAt("p_p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) {
+ SetupTest(R"(
+ MyObj* target(bool c1, bool c2) {
+ static MyObj global_obj;
+ MyObj local_obj1;
+ MyObj local_obj2;
+ MyObj* p = nullptr;
+ POINT(before_cond);
+ if(c1){
+ p = &local_obj1;
+ POINT(after_c1);
+ return p;
+ }
+ else if(c2){
+ p = &local_obj2;
+ POINT(after_c2);
+ return p;
+ }
+ p = &global_obj;
+ POINT(after_cond);
+ return p;
+ }
+ )");
+ // Expect two permissive warnings here in each block
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond"));
+ EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_cond"));
+
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2"));
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2"));
+
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1"));
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond"));
+}
+
+TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) {
+ SetupTest(R"(
+ MyObj* target(bool c1, bool c2) {
+ static MyObj global_obj;
+ MyObj local_obj1;
+ MyObj local_obj2;
+ MyObj* p = nullptr;
+ POINT(before_cond);
+ if(c1){
+ p = &local_obj1;
+ POINT(after_c1);
+ }
+ else if(c2){
+ p = &local_obj2;
+ POINT(after_c2);
+ }
+ else{
+ p = &global_obj;
+ POINT(after_else);
+ }
+
+ POINT(after_cond);
+ return p;
+ }
+ )");
+ // Expect permissive warning at return statement with both local_obj1 and
+ // local_obj2 as culprit loans
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_cond"));
+ EXPECT_THAT(
+ Origin("p"),
+ HasLoansTo({"global_obj", "local_obj2", "local_obj1"}, "after_cond"));
+
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_else"));
+ EXPECT_THAT(Origin("p"), HasLoansTo({"global_obj"}, "after_else"));
+
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c2"));
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj2"}, "after_c2"));
+
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("after_c1"));
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj1"}, "after_c1"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({}, "before_cond"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("before_cond"));
+}
+
+TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) {
+ SetupTest(R"(
+ MyObj* target() {
+ MyObj* p;
+ {
+ MyObj local_obj;
+ p = &local_obj;
+ POINT(p1);
+ }
+ POINT(p2);
+ return p;
+ }
+ )");
+ // Expect a Use-after-scope warning because `p` in `return p` is a use even
+ // before return
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
+ SetupTest(R"(
+ MyObj* target(bool c) {
+ MyObj* p;
+ static MyObj global_obj;
+ {
+ MyObj local_obj;
+ p = &local_obj;
+ if(c){
+ POINT(p1);
+ return p;
+ }
+
+ }
+ POINT(p2);
+ return &global_obj;
+ }
+ )");
+ // Expect a return stack address warning as return is before use after scope
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+
+ EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
} // anonymous namespace
} // namespace clang::lifetimes::internal
More information about the cfe-commits
mailing list