[clang] [LifetimeSafety] Issue 164963 detect manual construction/destruction (PR #192504)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 16 11:23:32 PDT 2026
https://github.com/NeKon69 created https://github.com/llvm/llvm-project/pull/192504
Still in progress
>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 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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())
More information about the cfe-commits
mailing list