[clang] [LifetimeSafety] Add support for `new`/`delete` (PR #192504)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Apr 19 08:13:30 PDT 2026
https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/192504
>From 91892037592d54ead8ea32302daf260ad0b95554 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 13 Apr 2026 11:40:31 +0300
Subject: [PATCH 01/16] add declaration for DestroyOriginFact
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 6be8f6e455bc2..00b3a3e7ba27f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,6 +55,8 @@ class Fact {
OriginEscapes,
/// An origin is invalidated (e.g. vector resized).
InvalidateOrigin,
+ // An origin is manually destroyed (e.g. `delete`, manual destructor call).
+ DestroyOrigin,
};
private:
@@ -299,6 +301,24 @@ class MovedOriginFact : public Fact {
const OriginManager &OM) const override;
};
+class DestroyOriginFact : public Fact {
+ OriginID OID;
+ const Expr *DestroyExpr;
+
+public:
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::DestroyOrigin;
+ }
+
+ DestroyOriginFact(OriginID OID, const Expr *DestroyExpr)
+ : Fact(Kind::DestroyOrigin), OID(OID), DestroyExpr(DestroyExpr) {}
+
+ OriginID getDestroyedOrigin() const { return OID; }
+ const Expr *getDestroyExpr() const { return DestroyExpr; }
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override;
+};
+
/// A dummy-fact used to mark a specific point in the code for testing.
/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
class TestPointFact : public Fact {
>From 2934b47540ad102cb9cf8c5d4b1030083f4821e4 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 13 Apr 2026 11:43:22 +0300
Subject: [PATCH 02/16] add new fact definition
---
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 1bc0521a72359..0f7f234dc7860 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -98,6 +98,13 @@ void InvalidateOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << ")\n";
}
+void DestroyOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const {
+ OS << "DestroyOrigin (";
+ OM.dump(getDestroyedOrigin(), OS);
+ OS << ")\n";
+}
+
void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &) const {
OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
>From d7450dacbb8fdc252c380d71c3c0791bca0c7831 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 14 Apr 2026 21:56:50 +0300
Subject: [PATCH 03/16] Support `new`
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 +
.../LifetimeSafety/FactsGenerator.cpp | 35 +++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 2dbadb27981a7..d735175f1f00f 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -52,6 +52,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
void VisitLambdaExpr(const LambdaExpr *LE);
void VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE);
+ void VisitCXXNewExpr(const CXXNewExpr *NE);
private:
OriginList *getOriginsList(const ValueDecl &D);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 82b890b57817e..1f110d35af3d4 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -586,6 +586,41 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
}
+void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
+ NE->dumpColor();
+
+ OriginList *NList = getOriginsList(*NE)->peelOuterOrigin();
+ const auto FlowOrigins = [&](const auto &T) {
+ if (OriginList *ArgList = getOriginsList(*T); ArgList && NList)
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ NList->getOuterOriginID(), ArgList->getOuterOriginID(),
+ /*Kill=*/true));
+ };
+
+ if (auto *CE = NE->getConstructExpr()) {
+ VisitCXXConstructExpr(CE);
+ FlowOrigins(CE);
+ return;
+ }
+
+ if (auto *E = NE->getInitializer()) {
+ if (!NE->isArray()) {
+ FlowOrigins(E);
+ return;
+ }
+ if (const auto *ILE = dyn_cast<InitListExpr>(E); ILE) {
+ // FIXME: Right now this still overwrites the other origins. Probably will
+ // be fixed once OriginTree is in.
+ // We traverse the Init list in reverse order to prefer origins from the
+ // beginning.
+ for (unsigned i = ILE->getNumInits(); i > 0; i--) {
+ FlowOrigins(ILE->getInit(i - 1));
+ }
+ return;
+ }
+ }
+}
+
bool FactsGenerator::escapesViaReturn(OriginID OID) const {
return llvm::any_of(EscapesInCurrentBlock, [OID](const Fact *F) {
if (const auto *EF = F->getAs<ReturnEscapeFact>())
>From e009d230ee54f4338ae5c15b01581be4912f492f Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 16 Apr 2026 18:20:55 +0300
Subject: [PATCH 04/16] finish c++ new expression
---
clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 ++
.../LifetimeSafety/FactsGenerator.cpp | 53 +++++++++----------
.../LifetimeSafety/LoanPropagation.cpp | 1 +
3 files changed, 30 insertions(+), 27 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 0f64ac8a36ef7..48f112521d27f 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -180,6 +180,8 @@ class DataflowAnalysis {
return D->transfer(In, *F->getAs<TestPointFact>());
case Fact::Kind::InvalidateOrigin:
return D->transfer(In, *F->getAs<InvalidateOriginFact>());
+ case Fact::Kind::DestroyOrigin:
+ return D->transfer(In, *F->getAs<DestroyOriginFact>());
}
llvm_unreachable("Unknown fact kind");
}
@@ -193,6 +195,7 @@ class DataflowAnalysis {
Lattice transfer(Lattice In, const UseFact &) { return In; }
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; }
+ Lattice transfer(Lattice In, const DestroyOriginFact &) { return In; }
};
} // namespace clang::lifetimes::internal
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 1f110d35af3d4..8550b5e5d9283 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -587,36 +587,35 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
}
void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
- NE->dumpColor();
-
- OriginList *NList = getOriginsList(*NE)->peelOuterOrigin();
- const auto FlowOrigins = [&](const auto &T) {
- if (OriginList *ArgList = getOriginsList(*T); ArgList && NList)
+ OriginList *NewList = getOriginsList(*NE);
+
+ // Check if we have a placement new where the second argument is void*, to
+ // avoid flowing from std::nothrow and the placement parameter amount is 1,
+ // that is to mostly limit to standard library placement new
+ if (NE->getNumPlacementArgs() == 1) {
+ if (const auto *Arg = NE->getOperatorNew()
+ ->getParamDecl(1)
+ ->getType()
+ ->getAs<PointerType>();
+ Arg && Arg->isVoidPointerType()) {
+ OriginList *PlacementList = getOriginsList(*NE->getPlacementArg(0));
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
- NList->getOuterOriginID(), ArgList->getOuterOriginID(),
- /*Kill=*/true));
- };
-
- if (auto *CE = NE->getConstructExpr()) {
- VisitCXXConstructExpr(CE);
- FlowOrigins(CE);
- return;
+ NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
+ true));
+ }
}
- if (auto *E = NE->getInitializer()) {
- if (!NE->isArray()) {
- FlowOrigins(E);
- return;
- }
- if (const auto *ILE = dyn_cast<InitListExpr>(E); ILE) {
- // FIXME: Right now this still overwrites the other origins. Probably will
- // be fixed once OriginTree is in.
- // We traverse the Init list in reverse order to prefer origins from the
- // beginning.
- for (unsigned i = ILE->getNumInits(); i > 0; i--) {
- FlowOrigins(ILE->getInit(i - 1));
- }
- return;
+ NewList = NewList->peelOuterOrigin();
+
+ if (auto *CE = NE->getConstructExpr(); CE) {
+ if (OriginList *ArgList = getOriginsList(*CE); ArgList && NewList)
+ flow(NewList, ArgList, true);
+ } else if (const Expr *E = NE->getInitializer(); E) {
+ if (const auto *ILE = dyn_cast<InitListExpr>(E); NE->isArray() && ILE) {
+ if (OriginList *InitList = getOriginsList(*ILE); InitList && NewList)
+ flow(NewList, InitList, true);
+ } else if (OriginList *ArgList = getOriginsList(*E); ArgList && NewList) {
+ flow(NewList, ArgList, true);
}
}
}
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index e437fb7d41268..fdde0121c4e5b 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -68,6 +68,7 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
case Fact::Kind::Expire:
case Fact::Kind::TestPoint:
case Fact::Kind::InvalidateOrigin:
+ case Fact::Kind::DestroyOrigin:
break;
}
}
>From 775dbdf5ea8743661236a9d0270c9ed4c4ec2304 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 16 Apr 2026 21:17:23 +0300
Subject: [PATCH 05/16] add fact handling, introduce `delete` visitor
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 +
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 31 ++++++++++++++++++-
.../LifetimeSafety/FactsGenerator.cpp | 18 +++++++++--
3 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index d735175f1f00f..a88c51e4cf6ef 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -53,6 +53,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitLambdaExpr(const LambdaExpr *LE);
void VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE);
void VisitCXXNewExpr(const CXXNewExpr *NE);
+ void VisitCXXDeleteExpr(const CXXDeleteExpr *DE);
private:
OriginList *getOriginsList(const ValueDecl &D);
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 36477c6f67b52..004158143f5b7 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -98,6 +98,8 @@ class LifetimeChecker {
checkInvalidation(IOF);
else if (const auto *OEF = F->getAs<OriginEscapesFact>())
checkAnnotations(OEF);
+ else if (const auto *DOF = F->getAs<DestroyOriginFact>())
+ checkDestroyed(DOF);
issuePendingWarnings();
suggestAnnotations();
reportNoescapeViolations();
@@ -219,6 +221,32 @@ class LifetimeChecker {
}
}
+ void checkDestroyed(const DestroyOriginFact *DOF) {
+ OriginID DestroyedOrigin = DOF->getDestroyedOrigin();
+ LoanSet DirectlyDestroyedLoans =
+ LoanPropagation.getLoans(DestroyedOrigin, DOF);
+ LivenessMap Origins = LiveOrigins.getLiveOriginsAt(DOF);
+ for (auto &[OID, LiveInfo] : Origins) {
+ LoanSet HeldLoans = LoanPropagation.getLoans(OID, DOF);
+ for (LoanID DestroyedLoanID : HeldLoans) {
+ if (!DirectlyDestroyedLoans.contains(DestroyedLoanID))
+ continue;
+
+ bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
+ bool LastDomination =
+ FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
+ if (!LastDomination) {
+ FinalWarningsMap[DestroyedLoanID] = {
+ /*ExpiryLoc=*/{},
+ /*CausingFact=*/LiveInfo.CausingFact,
+ /*MovedExpr=*/nullptr,
+ /*InvalidatedByExpr=*/DOF->getDestroyExpr(),
+ /*CausingFactDominatesExpiry=*/CurDomination};
+ }
+ }
+ }
+ }
+
void issuePendingWarnings() {
if (!SemaHelper)
return;
@@ -269,7 +297,8 @@ class LifetimeChecker {
}
/// Returns the declaration of a function that is visible across translation
- /// units, if such a declaration exists and is different from the definition.
+ /// units, if such a declaration exists and is different from the
+ /// definition.
static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
SourceManager &SM) {
if (!FD.isExternallyVisible())
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8550b5e5d9283..8f7a563c21b81 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -607,19 +607,31 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
NewList = NewList->peelOuterOrigin();
+ if (!NewList)
+ return;
+
if (auto *CE = NE->getConstructExpr(); CE) {
- if (OriginList *ArgList = getOriginsList(*CE); ArgList && NewList)
+ if (OriginList *ArgList = getOriginsList(*CE); ArgList)
flow(NewList, ArgList, true);
} else if (const Expr *E = NE->getInitializer(); E) {
if (const auto *ILE = dyn_cast<InitListExpr>(E); NE->isArray() && ILE) {
- if (OriginList *InitList = getOriginsList(*ILE); InitList && NewList)
+ if (OriginList *InitList = getOriginsList(*ILE); InitList)
flow(NewList, InitList, true);
- } else if (OriginList *ArgList = getOriginsList(*E); ArgList && NewList) {
+ } else if (OriginList *ArgList = getOriginsList(*E); ArgList) {
flow(NewList, ArgList, true);
}
}
}
+void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
+ OriginList *List = getOriginsList(*DE->getArgument())->peelOuterOrigin();
+ while (List) {
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
+ List = List->peelOuterOrigin();
+ }
+}
+
bool FactsGenerator::escapesViaReturn(OriginID OID) const {
return llvm::any_of(EscapesInCurrentBlock, [OID](const Fact *F) {
if (const auto *EF = F->getAs<ReturnEscapeFact>())
>From 3f190e9c35ce873dae633f01f3da1534382b6737 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 16 Apr 2026 21:21:01 +0300
Subject: [PATCH 06/16] revert comment formatting change
---
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 004158143f5b7..c3945df5c88de 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -297,8 +297,7 @@ class LifetimeChecker {
}
/// Returns the declaration of a function that is visible across translation
- /// units, if such a declaration exists and is different from the
- /// definition.
+ /// units, if such a declaration exists and is different from the definition.
static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
SourceManager &SM) {
if (!FD.isExternallyVisible())
>From eb9e40c757fb49241cc1006a55303e24d608e02c Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 18:38:21 +0300
Subject: [PATCH 07/16] [LifetimeSafety] finish support for new/delete
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 1 +
.../Analyses/LifetimeSafety/LifetimeSafety.h | 9 +++++---
.../Analysis/Analyses/LifetimeSafety/Loans.h | 14 ++++++++----
clang/include/clang/Basic/DiagnosticGroups.td | 12 ++++++++--
.../clang/Basic/DiagnosticSemaKinds.td | 5 +++++
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 9 +++++---
.../LifetimeSafety/FactsGenerator.cpp | 22 ++++++++++++++-----
clang/lib/Analysis/LifetimeSafety/Loans.cpp | 4 ++++
clang/lib/Sema/SemaLifetimeSafety.h | 16 +++++++++++---
9 files changed, 71 insertions(+), 21 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 00b3a3e7ba27f..7dc31c0eebbb9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -301,6 +301,7 @@ class MovedOriginFact : public Fact {
const OriginManager &OM) const override;
};
+// Inner origin has been destroyed, e.g. via `delete`, manaul destructor call.
class DestroyOriginFact : public Fact {
OriginID OID;
const Expr *DestroyExpr;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 08038dd096685..e8eecf1bd7c3d 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -60,15 +60,18 @@ class LifetimeSafetySemaHelper {
LifetimeSafetySemaHelper() = default;
virtual ~LifetimeSafetySemaHelper() = default;
- virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
- const Expr *MovedExpr,
- SourceLocation FreeLoc) {}
+ virtual void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
+ const Expr *MovedExpr,
+ SourceLocation FreeLoc) {}
virtual void reportUseAfterReturn(const Expr *IssueExpr,
const Expr *ReturnExpr,
const Expr *MovedExpr,
SourceLocation ExpiryLoc) {}
+ virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
+ const Expr *FreedExpr) {}
+
virtual void reportDanglingField(const Expr *IssueExpr,
const FieldDecl *Field,
const Expr *MovedExpr,
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index aee6bf9eb69c9..ff942d9425a89 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -47,20 +47,22 @@ class AccessPath {
ValueDecl,
MaterializeTemporary,
PlaceholderParam,
- PlaceholderThis
+ PlaceholderThis,
+ HeapAllocation,
};
private:
Kind K;
- const llvm::PointerUnion<const clang::ValueDecl *,
- const clang::MaterializeTemporaryExpr *,
- const ParmVarDecl *, const CXXMethodDecl *>
+ const llvm::PointerUnion<
+ const clang::ValueDecl *, const clang::MaterializeTemporaryExpr *,
+ const ParmVarDecl *, const CXXMethodDecl *, const CXXNewExpr *>
Root;
public:
AccessPath(const clang::ValueDecl *D) : K(Kind::ValueDecl), Root(D) {}
AccessPath(const clang::MaterializeTemporaryExpr *MTE)
: K(Kind::MaterializeTemporary), Root(MTE) {}
+ AccessPath(const CXXNewExpr *New) : K(Kind::HeapAllocation), Root(New) {}
static AccessPath Placeholder(const ParmVarDecl *PVD) {
return AccessPath(Kind::PlaceholderParam, PVD);
}
@@ -88,6 +90,10 @@ class AccessPath {
return K == Kind::PlaceholderThis ? Root.dyn_cast<const CXXMethodDecl *>()
: nullptr;
}
+ const CXXNewExpr *getAsHeapAllocation() const {
+ return K == Kind::HeapAllocation ? Root.dyn_cast<const CXXNewExpr *>()
+ : nullptr;
+ }
bool operator==(const AccessPath &RHS) const {
return K == RHS.K && Root == RHS.Root;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 98902b3d9caa8..408aae53eb0c2 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -586,6 +586,12 @@ This may contain false-positives, e.g. when the borrowed storage is potentially
}];
}
+def LifetimeSafetyUseAfterFree : DiagGroup<"lifetime-safety-use-after-free"> {
+ code Documentation = [{
+Warning to detect use-after-free, introduced by freeing an object and later using it.
+ }];
+}
+
// Dangling-Field (aka Escape-To-Field)
def LifetimeSafetyDanglingField : DiagGroup<"lifetime-safety-dangling-field"> {
code Documentation = [{
@@ -622,14 +628,16 @@ def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
[LifetimeSafetyUseAfterScope,
LifetimeSafetyReturnStackAddr,
LifetimeSafetyDanglingField,
- LifetimeSafetyDanglingGlobal]>;
+ LifetimeSafetyDanglingGlobal,
+ LifetimeSafetyUseAfterFree]>;
def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
[LifetimeSafetyUseAfterScopeMoved,
LifetimeSafetyReturnStackAddrMoved,
LifetimeSafetyDanglingFieldMoved,
LifetimeSafetyDanglingGlobal,
- LifetimeSafetyInvalidation]>;
+ LifetimeSafetyInvalidation,
+ LifetimeSafetyUseAfterFree]>;
def LifetimeSafety : DiagGroup<"lifetime-safety",
[LifetimeSafetyPermissive, LifetimeSafetyStrict]> {
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6d2fae551566f..9466d7a68ff15 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10962,6 +10962,10 @@ def warn_lifetime_safety_use_after_scope_moved : Warning<
"This could be false positive as the storage may have been moved later">,
InGroup<LifetimeSafetyUseAfterScopeMoved>, DefaultIgnore;
+def warn_lifetime_safety_use_after_free : Warning<
+ "allocated object does not live long enough">,
+ InGroup<LifetimeSafetyUseAfterFree>, DefaultIgnore;
+
def warn_lifetime_safety_return_stack_addr
: Warning<"address of stack memory is returned later">,
InGroup<LifetimeSafetyReturnStackAddr>,
@@ -11002,6 +11006,7 @@ def warn_lifetime_safety_dangling_global_moved
def note_lifetime_safety_used_here : Note<"later used here">;
def note_lifetime_safety_invalidated_here : Note<"invalidated here">;
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
+def note_lifetime_safety_freed_here : Note<"freed here">;
def note_lifetime_safety_returned_here : Note<"returned here">;
def note_lifetime_safety_moved_here : Note<"potentially moved here">;
def note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index c3945df5c88de..ec973ca7be9cb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -262,7 +262,10 @@ class LifetimeChecker {
if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
if (Warning.InvalidatedByExpr) {
- if (IssueExpr)
+ if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(IssueExpr); NE)
+ SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(),
+ Warning.InvalidatedByExpr);
+ else if (IssueExpr)
// Use-after-invalidation of an object on stack.
SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
Warning.InvalidatedByExpr);
@@ -273,8 +276,8 @@ class LifetimeChecker {
} else
// Scope-based expiry (use-after-scope).
- SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
- ExpiryLoc);
+ SemaHelper->reportUseAfterScope(IssueExpr, UF->getUseExpr(),
+ MovedExpr, ExpiryLoc);
} else if (const auto *OEF =
CausingFact.dyn_cast<const OriginEscapesFact *>()) {
if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8f7a563c21b81..a05c7624d10b8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,6 +94,14 @@ static const Loan *createLoan(FactManager &FactMgr,
return FactMgr.getLoanMgr().createLoan(Path, MTE);
}
+/// Creates a loan for the heap allocation
+/// \param NE The CXXNewExpr that represents the allocation
+/// \return The new Loan on success, nullptr otherwise
+static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) {
+ AccessPath Path(NE);
+ return FactMgr.getLoanMgr().createLoan(Path, NE);
+}
+
void FactsGenerator::run() {
llvm::TimeTraceScope TimeProfile("FactGenerator");
const CFG &Cfg = *AC.getCFG();
@@ -605,6 +613,10 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
}
}
+ const Loan *L = createLoan(FactMgr, NE);
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
+
NewList = NewList->peelOuterOrigin();
if (!NewList)
@@ -624,12 +636,10 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
- OriginList *List = getOriginsList(*DE->getArgument())->peelOuterOrigin();
- while (List) {
- CurrentBlockFacts.push_back(
- FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
- List = List->peelOuterOrigin();
- }
+ OriginList *List =
+ getOriginsList(*DE->getArgument()->IgnoreImpCasts())->peelOuterOrigin();
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
}
bool FactsGenerator::escapesViaReturn(OriginID OID) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 336331b8f5a27..09da7467af9eb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -28,6 +28,10 @@ void AccessPath::dump(llvm::raw_ostream &OS) const {
case Kind::PlaceholderThis:
OS << "$this";
break;
+ case Kind::HeapAllocation:
+ if (const auto *E = getAsHeapAllocation())
+ OS << "HeapAllocation at " << E;
+ break;
}
}
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index e6f7e3d929f61..46504bd77e0b5 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -43,9 +43,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
public:
LifetimeSafetySemaHelperImpl(Sema &S) : S(S) {}
- void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
- const Expr *MovedExpr,
- SourceLocation FreeLoc) override {
+ void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
+ const Expr *MovedExpr,
+ SourceLocation FreeLoc) override {
S.Diag(IssueExpr->getExprLoc(),
MovedExpr ? diag::warn_lifetime_safety_use_after_scope_moved
: diag::warn_lifetime_safety_use_after_scope)
@@ -58,6 +58,16 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
<< UseExpr->getSourceRange();
}
+ void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
+ const Expr *FreedExpr) override {
+ S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_use_after_free)
+ << IssueExpr->getSourceRange();
+ S.Diag(FreedExpr->getExprLoc(), diag::note_lifetime_safety_freed_here)
+ << FreedExpr->getSourceRange();
+ S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
+ << UseExpr->getSourceRange();
+ }
+
void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
const Expr *MovedExpr,
SourceLocation ExpiryLoc) override {
>From a8e57bc36a621e65b8f7622c795d6e9d1b41640e Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 20:58:17 +0300
Subject: [PATCH 08/16] a few bug fixes and added tests
---
.../LifetimeSafety/FactsGenerator.cpp | 19 +-
clang/test/Sema/Inputs/lifetime-analysis.h | 3 +
clang/test/Sema/warn-lifetime-safety.cpp | 254 ++++++++++++++++++
3 files changed, 270 insertions(+), 6 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index a05c7624d10b8..4d57bf3d484d1 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -319,6 +319,12 @@ void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
case CK_BuiltinFnToFnPtr:
// Ignore function-to-pointer decays.
return;
+ case CK_BitCast:
+ // Only flow if the shapes are the same (e.g. casting from int** to void*
+ // will not flow here)
+ if (Src && Dest && Dest->getLength() == Src->getLength())
+ flow(Dest, Src, /*Kill=*/true);
+ return;
default:
return;
}
@@ -611,12 +617,12 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
true));
}
+ } else {
+ const Loan *L = createLoan(FactMgr, NE);
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
}
- const Loan *L = createLoan(FactMgr, NE);
- CurrentBlockFacts.push_back(
- FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
-
NewList = NewList->peelOuterOrigin();
if (!NewList)
@@ -636,8 +642,9 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
- OriginList *List =
- getOriginsList(*DE->getArgument()->IgnoreImpCasts())->peelOuterOrigin();
+ OriginList *List = getOriginsList(*DE->getArgument()->IgnoreImpCasts());
+ if (List = List->peelOuterOrigin(); !List)
+ return;
CurrentBlockFacts.push_back(
FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
}
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index d1e847d20cc50..2f38041f68c80 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -269,3 +269,6 @@ template<class T> struct is_pointer : false_type {};
template<class T> struct is_pointer<T*> : true_type {};
template<class T> struct is_pointer<T* const> : true_type {};
}
+
+void *operator new(unsigned long, void *) noexcept;
+void *operator new[](unsigned long, void *) noexcept;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 77d8e3370676d..5288291bd4411 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2531,3 +2531,257 @@ int *noreturn_dead_nested(bool cond, bool cond2, int *num) {
}
} // namespace conditional_operator_control_flow
+
+namespace heap_allocation {
+
+//===----------------------------------------------------------------------===//
+// new
+//===----------------------------------------------------------------------===//
+
+void new_view_from_dead_scope() {
+ View *p;
+ {
+ MyObj obj;
+ p = new View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ p->use(); // expected-note {{later used here}}
+}
+
+void new_int_basic() {
+ int *p = new int; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void new_int_parens() {
+ int *p = new int(); // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void new_int_braces() {
+ int *p = new int{}; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void new_pointer_from_pointer() {
+ MyObj **p;
+ {
+ MyObj obj;
+ MyObj *q = &obj; // expected-warning {{object whose reference is captured does not live long enough}}
+ p = new MyObj *(q);
+ } // expected-note {{destroyed here}}
+ (void)**p; // expected-note {{later used here}}
+}
+
+void new_pointer_from_dead_object() {
+ MyObj **p;
+ {
+ MyObj obj;
+ p = new MyObj *(&obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)**p; // expected-note {{later used here}}
+}
+
+void new_array_basic() {
+ int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
+ delete[] p; // expected-note {{freed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+void new_array_parens() {
+ int *p = new int[2](); // expected-warning {{allocated object does not live long enough}}
+ delete[] p; // expected-note {{freed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+void new_array_braces() {
+ int *p = new int[2]{}; // expected-warning {{allocated object does not live long enough}}
+ delete[] p; // expected-note {{freed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+// FIXME: https://github.com/llvm/llvm-project/issues/187471
+void new_pointer_array_from_dead_objects() {
+ MyObj **arr;
+ {
+ MyObj a, b;
+ arr = new MyObj *[2]{&a, &b};
+ }
+ (void)arr[0]->id;
+ (void)arr[1]->id;
+}
+
+struct PointerArrayFieldHolder {
+ MyObj **Ptrs;
+};
+
+// FIXME: https://github.com/llvm/llvm-project/issues/187471
+void pointer_array_field_sensitivity() {
+ PointerArrayFieldHolder h;
+ {
+ MyObj a, b;
+ h.Ptrs = new MyObj *[2]{&a, &b};
+ }
+ (void)h.Ptrs[0]->id;
+}
+
+//===----------------------------------------------------------------------===//
+// placement new
+//===----------------------------------------------------------------------===//
+
+void placement_new_int_basic() {
+ int *p;
+ {
+ int storage;
+ p = new (&storage) int; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void placement_new_int_parens() {
+ int *p;
+ {
+ int storage;
+ p = new (&storage) int(); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void placement_new_int_braces() {
+ int *p;
+ {
+ int storage;
+ p = new (&storage) int{}; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void placement_new_view_from_dead_scope() {
+ View storage;
+ View *p = &storage;
+ {
+ MyObj obj;
+ p = new (&storage) View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ p->use(); // expected-note {{later used here}}
+}
+
+void placement_new_pointer_from_dead_object() {
+ MyObj *slot = nullptr;
+ MyObj **p = &slot;
+ {
+ MyObj obj;
+ p = new (&slot) MyObj *(&obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)**p; // expected-note {{later used here}}
+}
+
+void placement_new_array_basic() {
+ int *p;
+ {
+ int storage[2];
+ p = new (&storage) int[2]; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+void placement_new_array_parens() {
+ int *p;
+ {
+ int storage[2];
+ p = new (&storage) int[2](); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+void placement_new_array_braces() {
+ int *p;
+ {
+ int storage[2];
+ p = new (&storage) int[2]{}; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+//===----------------------------------------------------------------------===//
+// delete
+//===----------------------------------------------------------------------===//
+
+void delete_direct_use_after_free() {
+ MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)p->id; // expected-note {{later used here}}
+}
+
+void delete_alias_use_after_free() {
+ MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+ MyObj *q = p;
+ delete p; // expected-note {{freed here}}
+ (void)q->id; // expected-note {{later used here}}
+}
+
+void delete_pointer_propagation_use_after_free() {
+ MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+ MyObj **pp = &p;
+ delete p; // expected-note {{freed here}}
+ (void)(*pp)->id; // expected-note {{later used here}}
+}
+
+void delete_array_use_after_free() {
+ int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
+ delete[] p; // expected-note {{freed here}}
+ (void)p[1]; // expected-note {{later used here}}
+}
+
+void delete_nullptr_no_warning() {
+ int *p = nullptr;
+ delete p;
+}
+
+void delete_array_nullptr_no_warning() {
+ int *p = nullptr;
+ delete[] p;
+}
+
+struct ClassSpecificDelete {
+ int X;
+ static void operator delete(void *);
+};
+
+void class_specific_operator_delete_use_after_free() {
+ ClassSpecificDelete *p = new ClassSpecificDelete; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)p->X; // expected-note {{later used here}}
+}
+
+struct PointerFieldHolder {
+ MyObj *Ptr;
+};
+
+// FIXME: https://github.com/llvm/llvm-project/issues/184344
+void placement_new_pointer_field_use_after_scope() {
+ PointerFieldHolder h;
+ PointerFieldHolder *p = &h;
+ {
+ MyObj obj;
+ p = new (&h) PointerFieldHolder{&obj};
+ }
+ (void)p->Ptr->id;
+}
+
+// FIXME: https://github.com/llvm/llvm-project/issues/184344
+void delete_through_pointer_field() {
+ PointerFieldHolder h{new MyObj};
+ delete h.Ptr;
+ (void)h.Ptr->id;
+}
+
+void delete_stack_object() {
+ MyObj obj;
+ delete &obj;
+ (void)obj.id;
+}
+
+} // namespace heap_allocation
>From df5235980e19977ce09b76a303179e6c6a3a9e72 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 21:27:22 +0300
Subject: [PATCH 09/16] change wording in comment
---
clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 7dc31c0eebbb9..9a36bdaf41b37 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,7 +55,7 @@ class Fact {
OriginEscapes,
/// An origin is invalidated (e.g. vector resized).
InvalidateOrigin,
- // An origin is manually destroyed (e.g. `delete`, manual destructor call).
+ // An origin is manually destroyed (e.g. via `delete`).
DestroyOrigin,
};
@@ -301,7 +301,7 @@ class MovedOriginFact : public Fact {
const OriginManager &OM) const override;
};
-// Inner origin has been destroyed, e.g. via `delete`, manaul destructor call.
+// Inner origin has been destroyed, e.g. via `delete`.
class DestroyOriginFact : public Fact {
OriginID OID;
const Expr *DestroyExpr;
>From 576b37aeeb901491020c246ec96e6426f372879a Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 18 Apr 2026 23:34:38 +0300
Subject: [PATCH 10/16] fix windows test errors
---
clang/test/Sema/Inputs/lifetime-analysis.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 2ca0ad759e578..2836ec00e97cb 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -290,5 +290,5 @@ class function<R(Args...)> {
}
-void *operator new(unsigned long, void *) noexcept;
-void *operator new[](unsigned long, void *) noexcept;
+void *operator new(std::size_t, void *) noexcept;
+void *operator new[](std::size_t, void *) noexcept;
>From 1df6b221d1ac2ae06d4fb8e2f20aeacea05f01c8 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 17:31:44 +0300
Subject: [PATCH 11/16] address review comments, add a few more tests
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 2 +-
.../Analysis/Analyses/LifetimeSafety/Loans.h | 11 +++++-----
.../LifetimeSafety/FactsGenerator.cpp | 4 ++--
clang/lib/Analysis/LifetimeSafety/Loans.cpp | 2 +-
clang/test/Sema/warn-lifetime-safety.cpp | 22 +++++++++++++++++++
5 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 71556b5880914..400590656a35e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,7 +55,7 @@ class Fact {
OriginEscapes,
/// An origin is invalidated (e.g. vector resized).
InvalidateOrigin,
- // An origin is manually destroyed (e.g. via `delete`).
+ // An origin is explicitly destroyed (e.g. via `delete`).
DestroyOrigin,
/// All loans of an origin are cleared.
KillOrigin,
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index ff942d9425a89..f1415d8753bb0 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -35,12 +35,13 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
/// - MaterializeTemporaryExpr: a temporary object
/// - ParmVarDecl: a function parameter (placeholder)
/// - CXXMethodDecl: the implicit 'this' object (placeholder)
+/// - CXXNewExpr: a heap allocation made by `new`
///
/// Placeholder paths never expire within the function scope, as they represent
/// storage from the caller's scope.
///
/// TODO: Model access paths of other types, e.g. field, array subscript, heap
-/// and globals.
+/// allocation not through `new`, and globals.
class AccessPath {
public:
enum class Kind : uint8_t {
@@ -48,7 +49,7 @@ class AccessPath {
MaterializeTemporary,
PlaceholderParam,
PlaceholderThis,
- HeapAllocation,
+ NewAllocation,
};
private:
@@ -62,7 +63,7 @@ class AccessPath {
AccessPath(const clang::ValueDecl *D) : K(Kind::ValueDecl), Root(D) {}
AccessPath(const clang::MaterializeTemporaryExpr *MTE)
: K(Kind::MaterializeTemporary), Root(MTE) {}
- AccessPath(const CXXNewExpr *New) : K(Kind::HeapAllocation), Root(New) {}
+ AccessPath(const CXXNewExpr *New) : K(Kind::NewAllocation), Root(New) {}
static AccessPath Placeholder(const ParmVarDecl *PVD) {
return AccessPath(Kind::PlaceholderParam, PVD);
}
@@ -91,8 +92,8 @@ class AccessPath {
: nullptr;
}
const CXXNewExpr *getAsHeapAllocation() const {
- return K == Kind::HeapAllocation ? Root.dyn_cast<const CXXNewExpr *>()
- : nullptr;
+ return K == Kind::NewAllocation ? Root.dyn_cast<const CXXNewExpr *>()
+ : nullptr;
}
bool operator==(const AccessPath &RHS) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8536d62ddac1f..3c7a01c99522d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -334,7 +334,7 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
return;
case CK_BitCast:
// Only flow if the shapes are the same (e.g. casting from int** to void*
- // will not flow here)
+ // will not flow here).
if (Src && Dest && Dest->getLength() == Src->getLength())
flow(Dest, Src, /*Kill=*/true);
return;
@@ -634,7 +634,7 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
// Check if we have a placement new where the second argument is void*, to
// avoid flowing from std::nothrow and the placement parameter amount is 1,
- // that is to mostly limit to standard library placement new
+ // that is to mostly limit to standard library placement new.
if (NE->getNumPlacementArgs() == 1) {
if (const auto *Arg = NE->getOperatorNew()
->getParamDecl(1)
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 09da7467af9eb..e771995277ff0 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -28,7 +28,7 @@ void AccessPath::dump(llvm::raw_ostream &OS) const {
case Kind::PlaceholderThis:
OS << "$this";
break;
- case Kind::HeapAllocation:
+ case Kind::NewAllocation:
if (const auto *E = getAsHeapAllocation())
OS << "HeapAllocation at " << E;
break;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 83633c3a5629d..f5d833ac77c5c 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2696,6 +2696,13 @@ void new_int_braces() {
(void)*p; // expected-note {{later used here}}
}
+void conditional_delete(bool cond) {
+ int *p1 = new int; // expected-warning {{allocated object does not live long enough}}
+ int *p2 = new int;
+ delete (cond ? p1 : p2); // expected-note {{freed here}}
+ (void)*p1; // expected-note {{later used here}}
+}
+
void new_pointer_from_pointer() {
MyObj **p;
{
@@ -2715,6 +2722,20 @@ void new_pointer_from_dead_object() {
(void)**p; // expected-note {{later used here}}
}
+struct MultiView {
+ MultiView(MyObj& a [[clang::lifetimebound]], MyObj& b [[clang::lifetimebound]]);
+};
+
+void new_multiview_from_mixed_scope() {
+ MyObj obj1;
+ MultiView *p;
+ {
+ MyObj obj2;
+ p = new MultiView(obj1, obj2); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)p; // expected-note {{later used here}}
+}
+
void new_array_basic() {
int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
delete[] p; // expected-note {{freed here}}
@@ -2916,6 +2937,7 @@ void delete_stack_object() {
}
} // namespace heap_allocation
+
namespace method_call_uses_field_origins {
int GLOBAL_INT;
std::string GLOBAL_STRING{"123"};
>From 2bfc98f7f24f2fcbc5b00d5c540857b10c3889f0 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 17:39:53 +0300
Subject: [PATCH 12/16] add new test case
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 4 +---
clang/test/Sema/warn-lifetime-safety.cpp | 10 ++++++++++
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 3c7a01c99522d..4c7146ff1fbe0 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -671,9 +671,7 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
- OriginList *List = getOriginsList(*DE->getArgument()->IgnoreImpCasts());
- if (List = List->peelOuterOrigin(); !List)
- return;
+ OriginList *List = getOriginsList(*DE->getArgument());
CurrentBlockFacts.push_back(
FactMgr.createFact<DestroyOriginFact>(List->getOuterOriginID(), DE));
}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index f5d833ac77c5c..1de413c490bce 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2703,6 +2703,16 @@ void conditional_delete(bool cond) {
(void)*p1; // expected-note {{later used here}}
}
+int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
+
+void delete_returned_from_call() {
+ int* x = new int(1); // expected-warning {{allocated object does not live long enough}}
+ int* y = new int(2); // expected-warning {{allocated object does not live long enough}}
+ delete foo(x, y); // expected-note 2 {{freed here}}
+ (void)x; // expected-note {{later used here}}
+ (void)y; // expected-note {{later used here}}
+}
+
void new_pointer_from_pointer() {
MyObj **p;
{
>From c5548a494b681cac8c117f021ed767766e9d12f6 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 17:41:15 +0300
Subject: [PATCH 13/16] update the comment
---
clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 400590656a35e..3bdb9d977d8c9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -303,7 +303,7 @@ class MovedOriginFact : public Fact {
const OriginManager &OM) const override;
};
-// Inner origin has been destroyed, e.g. via `delete`.
+// Origin has been destroyed, e.g. via `delete`.
class DestroyOriginFact : public Fact {
OriginID OID;
const Expr *DestroyExpr;
>From 534eb5097cca7c276914ae53378fd7473ac4e09a Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:05:59 +0300
Subject: [PATCH 14/16] refactor logic for recording warnings
---
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 78 ++++++++++---------
1 file changed, 41 insertions(+), 37 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index ce7fa385c996f..f363eecc5153b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -187,6 +187,32 @@ class LifetimeChecker {
}
}
+ template <typename OriginFact, typename Predicate>
+ void recordWarningsForMatchingLoans(const Expr *InvalidationExpr,
+ LoanSet &DirectlyAffectedLoans,
+ LivenessMap &Origins, OriginFact OF,
+ Predicate Pred) {
+ for (auto &[OID, LiveInfo] : Origins) {
+ LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
+ for (LoanID DestroyedLoanID : HeldLoans) {
+ if (!Pred(DestroyedLoanID))
+ continue;
+
+ bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
+ bool LastDomination =
+ FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
+ if (!LastDomination) {
+ FinalWarningsMap[DestroyedLoanID] = {
+ /*ExpiryLoc=*/{},
+ /*CausingFact=*/LiveInfo.CausingFact,
+ /*MovedExpr=*/nullptr,
+ /*InvalidatedByExpr=*/InvalidationExpr,
+ /*CausingFactDominatesExpiry=*/CurDomination};
+ }
+ }
+ }
+ }
+
/// Checks for use-after-invalidation errors when a container is modified.
///
/// This method identifies origins that are live at the point of invalidation
@@ -197,9 +223,11 @@ class LifetimeChecker {
/// Get loans directly pointing to the invalidated container
LoanSet DirectlyInvalidatedLoans =
LoanPropagation.getLoans(InvalidatedOrigin, IOF);
- auto IsInvalidated = [&](const Loan *L) {
+ auto IsInvalidated = [&](const LoanID &LID) {
for (LoanID InvalidID : DirectlyInvalidatedLoans) {
const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
+ const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
+
if (InvalidL->getAccessPath() == L->getAccessPath())
return true;
}
@@ -207,23 +235,9 @@ class LifetimeChecker {
};
// For each live origin, check if it holds an invalidated loan and report.
LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
- for (auto &[OID, LiveInfo] : Origins) {
- LoanSet HeldLoans = LoanPropagation.getLoans(OID, IOF);
- for (LoanID LiveLoanID : HeldLoans)
- if (IsInvalidated(FactMgr.getLoanMgr().getLoan(LiveLoanID))) {
- bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
- bool LastDomination =
- FinalWarningsMap.lookup(LiveLoanID).CausingFactDominatesExpiry;
- if (!LastDomination) {
- FinalWarningsMap[LiveLoanID] = {
- /*ExpiryLoc=*/{},
- /*CausingFact=*/LiveInfo.CausingFact,
- /*MovedExpr=*/nullptr,
- /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
- /*CausingFactDominatesExpiry=*/CurDomination};
- }
- }
- }
+ recordWarningsForMatchingLoans(IOF->getInvalidationExpr(),
+ DirectlyInvalidatedLoans, Origins, IOF,
+ IsInvalidated);
}
void checkDestroyed(const DestroyOriginFact *DOF) {
@@ -231,25 +245,14 @@ class LifetimeChecker {
LoanSet DirectlyDestroyedLoans =
LoanPropagation.getLoans(DestroyedOrigin, DOF);
LivenessMap Origins = LiveOrigins.getLiveOriginsAt(DOF);
- for (auto &[OID, LiveInfo] : Origins) {
- LoanSet HeldLoans = LoanPropagation.getLoans(OID, DOF);
- for (LoanID DestroyedLoanID : HeldLoans) {
- if (!DirectlyDestroyedLoans.contains(DestroyedLoanID))
- continue;
- bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
- bool LastDomination =
- FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
- if (!LastDomination) {
- FinalWarningsMap[DestroyedLoanID] = {
- /*ExpiryLoc=*/{},
- /*CausingFact=*/LiveInfo.CausingFact,
- /*MovedExpr=*/nullptr,
- /*InvalidatedByExpr=*/DOF->getDestroyExpr(),
- /*CausingFactDominatesExpiry=*/CurDomination};
- }
- }
- }
+ auto IsDestroyed = [&DirectlyDestroyedLoans](const LoanID &LID) {
+ return DirectlyDestroyedLoans.contains(LID);
+ };
+
+ recordWarningsForMatchingLoans(DOF->getDestroyExpr(),
+ DirectlyDestroyedLoans, Origins, DOF,
+ IsDestroyed);
}
void issuePendingWarnings() {
@@ -305,7 +308,8 @@ class LifetimeChecker {
}
/// Returns the declaration of a function that is visible across translation
- /// units, if such a declaration exists and is different from the definition.
+ /// units, if such a declaration exists and is different from the
+ /// definition.
static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
SourceManager &SM) {
if (!FD.isExternallyVisible())
>From cb9700e9829ec69d4f812bd33291da5a5e254c22 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:09:20 +0300
Subject: [PATCH 15/16] rename variables
---
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index f363eecc5153b..6ea469525c049 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -188,25 +188,25 @@ class LifetimeChecker {
}
template <typename OriginFact, typename Predicate>
- void recordWarningsForMatchingLoans(const Expr *InvalidationExpr,
+ void recordWarningsForMatchingLoans(const Expr *InvalidatingExpr,
LoanSet &DirectlyAffectedLoans,
LivenessMap &Origins, OriginFact OF,
Predicate Pred) {
for (auto &[OID, LiveInfo] : Origins) {
LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
- for (LoanID DestroyedLoanID : HeldLoans) {
- if (!Pred(DestroyedLoanID))
+ for (LoanID HeldLoan : HeldLoans) {
+ if (!Pred(HeldLoan))
continue;
bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
bool LastDomination =
- FinalWarningsMap.lookup(DestroyedLoanID).CausingFactDominatesExpiry;
+ FinalWarningsMap.lookup(HeldLoan).CausingFactDominatesExpiry;
if (!LastDomination) {
- FinalWarningsMap[DestroyedLoanID] = {
+ FinalWarningsMap[HeldLoan] = {
/*ExpiryLoc=*/{},
/*CausingFact=*/LiveInfo.CausingFact,
/*MovedExpr=*/nullptr,
- /*InvalidatedByExpr=*/InvalidationExpr,
+ /*InvalidatedByExpr=*/InvalidatingExpr,
/*CausingFactDominatesExpiry=*/CurDomination};
}
}
>From 7bcc47f20f168a83a2cb59156b926ff96296f7e4 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 19 Apr 2026 18:13:10 +0300
Subject: [PATCH 16/16] change function signature
---
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 6ea469525c049..6c58ed8bc67cb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -189,10 +189,8 @@ class LifetimeChecker {
template <typename OriginFact, typename Predicate>
void recordWarningsForMatchingLoans(const Expr *InvalidatingExpr,
- LoanSet &DirectlyAffectedLoans,
- LivenessMap &Origins, OriginFact OF,
- Predicate Pred) {
- for (auto &[OID, LiveInfo] : Origins) {
+ OriginFact OF, Predicate Pred) {
+ for (auto &[OID, LiveInfo] : LiveOrigins.getLiveOriginsAt(OF)) {
LoanSet HeldLoans = LoanPropagation.getLoans(OID, OF);
for (LoanID HeldLoan : HeldLoans) {
if (!Pred(HeldLoan))
@@ -235,8 +233,7 @@ class LifetimeChecker {
};
// For each live origin, check if it holds an invalidated loan and report.
LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
- recordWarningsForMatchingLoans(IOF->getInvalidationExpr(),
- DirectlyInvalidatedLoans, Origins, IOF,
+ recordWarningsForMatchingLoans(IOF->getInvalidationExpr(), IOF,
IsInvalidated);
}
@@ -250,9 +247,7 @@ class LifetimeChecker {
return DirectlyDestroyedLoans.contains(LID);
};
- recordWarningsForMatchingLoans(DOF->getDestroyExpr(),
- DirectlyDestroyedLoans, Origins, DOF,
- IsDestroyed);
+ recordWarningsForMatchingLoans(DOF->getDestroyExpr(), DOF, IsDestroyed);
}
void issuePendingWarnings() {
More information about the cfe-commits
mailing list