https://github.com/AbhinavPradeep updated 
https://github.com/llvm/llvm-project/pull/177985

>From 8bae199a384392630a2189467c5be254b9abc05b Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Tue, 27 Jan 2026 01:20:55 +1000
Subject: [PATCH 1/2] Modify CFG to have a CFGFullExprCleanup marker.

---
 clang/include/clang/Analysis/CFG.h    | 32 ++++++++++++
 clang/lib/Analysis/CFG.cpp            | 71 ++++++++++++++++++++++++---
 clang/lib/Analysis/PathDiagnostic.cpp |  1 +
 3 files changed, 98 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Analysis/CFG.h 
b/clang/include/clang/Analysis/CFG.h
index a4bafd4927df0..c021ffcf85785 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -62,6 +62,7 @@ class CFGElement {
     NewAllocator,
     LifetimeEnds,
     LoopExit,
+    FullExprCleanup,
     // stmt kind
     Statement,
     Constructor,
@@ -313,6 +314,32 @@ class CFGLifetimeEnds : public CFGElement {
   }
 };
 
+class CFGFullExprCleanup : public CFGElement {
+  using MTEVecTy = BumpVector<const MaterializeTemporaryExpr *>;
+
+public:
+  explicit CFGFullExprCleanup(const MTEVecTy *vec)
+      : CFGElement(FullExprCleanup, vec, nullptr) {}
+
+  ArrayRef<const MaterializeTemporaryExpr *> getExpiringMTEs() const {
+    const MTEVecTy *ExpiringMTEs =
+        static_cast<const MTEVecTy *>(Data1.getPointer());
+    if (!ExpiringMTEs)
+      return {};
+    return ArrayRef<const MaterializeTemporaryExpr *>(ExpiringMTEs->begin(),
+                                                      ExpiringMTEs->end());
+  }
+
+private:
+  friend class CFGElement;
+
+  CFGFullExprCleanup() = default;
+
+  static bool isKind(const CFGElement &elem) {
+    return elem.getKind() == FullExprCleanup;
+  }
+};
+
 /// Represents beginning of a scope implicitly generated
 /// by the compiler on encountering a CompoundStmt
 class CFGScopeBegin : public CFGElement {
@@ -1183,6 +1210,11 @@ class CFGBlock {
     Elements.push_back(CFGLifetimeEnds(VD, S), C);
   }
 
+  void appendFullExprCleanup(BumpVector<const MaterializeTemporaryExpr *> *BV,
+                             BumpVectorContext &C) {
+    Elements.push_back(CFGFullExprCleanup(BV), C);
+  }
+
   void appendLoopExit(const Stmt *LoopStmt, BumpVectorContext &C) {
     Elements.push_back(CFGLoopExit(LoopStmt), C);
   }
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index a9c7baa00543c..03b3fd7efe89e 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -55,6 +55,7 @@
 #include "llvm/Support/TimeProfiler.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cassert>
+#include <cstddef>
 #include <memory>
 #include <optional>
 #include <string>
@@ -698,7 +699,9 @@ class CFGBuilder {
     TempDtorContext() = default;
     TempDtorContext(TryResult KnownExecuted)
         : IsConditional(true), KnownExecuted(KnownExecuted) {}
-
+    TempDtorContext(TryResult KnownExecuted, bool TrackExpiringMTEs)
+        : IsConditional(true), TrackExpiringMTEs(TrackExpiringMTEs),
+          KnownExecuted(KnownExecuted) {}
     /// Returns whether we need to start a new branch for a temporary 
destructor
     /// call. This is the case when the temporary destructor is
     /// conditionally executed, and it is the first one we encounter while
@@ -716,7 +719,16 @@ class CFGBuilder {
       TerminatorExpr = E;
     }
 
+    void track(const MaterializeTemporaryExpr *MTE) {
+      // Must only be invoked when TrackMTE is true
+      assert(TrackExpiringMTEs);
+      if (MTE)
+        CollectedMTEs.push_back(MTE);
+    }
+
     const bool IsConditional = false;
+    bool TrackExpiringMTEs = false;
+    SmallVector<const MaterializeTemporaryExpr *, 5> CollectedMTEs;
     const TryResult KnownExecuted = true;
     CFGBlock *Succ = nullptr;
     CXXBindTemporaryExpr *TerminatorExpr = nullptr;
@@ -803,6 +815,7 @@ class CFGBuilder {
   void addScopeChangesHandling(LocalScope::const_iterator SrcPos,
                                LocalScope::const_iterator DstPos,
                                Stmt *S);
+  void addFullExprCleanupMarker(TempDtorContext &Context);
   CFGBlock *createScopeChangesHandlingBlock(LocalScope::const_iterator SrcPos,
                                             CFGBlock *SrcBlk,
                                             LocalScope::const_iterator DstPost,
@@ -1816,8 +1829,11 @@ CFGBlock *CFGBuilder::addInitializer(CXXCtorInitializer 
*I) {
     if (BuildOpts.AddTemporaryDtors && HasTemporaries) {
       // Generate destructors for temporaries in initialization expression.
       TempDtorContext Context;
+      Context.TrackExpiringMTEs = true;
       VisitForTemporaryDtors(cast<ExprWithCleanups>(Init)->getSubExpr(),
                              /*ExternallyDestructed=*/false, Context);
+
+      addFullExprCleanupMarker(Context);
     }
   }
 
@@ -2065,6 +2081,23 @@ void 
CFGBuilder::addScopeChangesHandling(LocalScope::const_iterator SrcPos,
   addAutomaticObjHandling(SrcPos, BasePos, S);
 }
 
+void CFGBuilder::addFullExprCleanupMarker(TempDtorContext &Context) {
+  assert(Context.TrackExpiringMTEs);
+
+  using MTEVecTy = BumpVector<const MaterializeTemporaryExpr *>;
+  MTEVecTy *ExpiringMTEs = nullptr;
+  BumpVectorContext &BVC = cfg->getBumpVectorContext();
+
+  size_t NumCollected = Context.CollectedMTEs.size();
+  if (NumCollected > 0) {
+    autoCreateBlock();
+    ExpiringMTEs = new (cfg->getAllocator()) MTEVecTy(BVC, NumCollected);
+    for (const MaterializeTemporaryExpr *MTE : Context.CollectedMTEs)
+      ExpiringMTEs->push_back(MTE, BVC);
+    Block->appendFullExprCleanup(ExpiringMTEs, BVC);
+  }
+}
+
 /// createScopeChangesHandlingBlock - Creates a block with cfgElements
 /// corresponding to changing the scope from the source scope of the GotoStmt,
 /// to destination scope. Add destructor, lifetime and cfgScopeEnd
@@ -3113,8 +3146,11 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) {
     if (BuildOpts.AddTemporaryDtors && HasTemporaries) {
       // Generate destructors for temporaries in initialization expression.
       TempDtorContext Context;
+      Context.TrackExpiringMTEs = true;
       VisitForTemporaryDtors(cast<ExprWithCleanups>(Init)->getSubExpr(),
                              /*ExternallyDestructed=*/true, Context);
+
+      addFullExprCleanupMarker(Context);
     }
   }
 
@@ -4924,13 +4960,17 @@ CFGBlock 
*CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
 }
 
 CFGBlock *CFGBuilder::VisitExprWithCleanups(ExprWithCleanups *E,
-    AddStmtChoice asc, bool ExternallyDestructed) {
+                                            AddStmtChoice asc,
+                                            bool ExternallyDestructed) {
   if (BuildOpts.AddTemporaryDtors) {
     // If adding implicit destructors visit the full expression for adding
     // destructors of temporaries.
     TempDtorContext Context;
+    Context.TrackExpiringMTEs = true;
     VisitForTemporaryDtors(E->getSubExpr(), ExternallyDestructed, Context);
 
+    addFullExprCleanupMarker(Context);
+
     // Full expression has to be added as CFGStmt so it will be sequenced
     // before destructors of it's temporaries.
     asc = asc.withAlwaysAdd(true);
@@ -5117,6 +5157,8 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, 
bool ExternallyDestructed,
     case Stmt::MaterializeTemporaryExprClass: {
       const MaterializeTemporaryExpr* MTE = cast<MaterializeTemporaryExpr>(E);
       ExternallyDestructed = (MTE->getStorageDuration() != SD_FullExpression);
+      if (Context.TrackExpiringMTEs && !ExternallyDestructed)
+        Context.track(MTE);
       SmallVector<const Expr *, 2> CommaLHSs;
       SmallVector<SubobjectAdjustment, 2> Adjustments;
       // Find the expression whose lifetime needs to be extended.
@@ -5208,10 +5250,14 @@ CFGBlock 
*CFGBuilder::VisitBinaryOperatorForTemporaryDtors(
     // executed, thus we add a branch node that depends on the temporary
     // constructor call.
     TempDtorContext RHSContext(
-        bothKnownTrue(Context.KnownExecuted, RHSExecuted));
+        bothKnownTrue(Context.KnownExecuted, RHSExecuted),
+        Context.TrackExpiringMTEs);
     VisitForTemporaryDtors(E->getRHS(), false, RHSContext);
     InsertTempDtorDecisionBlock(RHSContext);
 
+    if (Context.TrackExpiringMTEs)
+      Context.CollectedMTEs.append(RHSContext.CollectedMTEs);
+
     return Block;
   }
 
@@ -5290,14 +5336,15 @@ CFGBlock 
*CFGBuilder::VisitConditionalOperatorForTemporaryDtors(
   if (NegatedVal.isKnown()) NegatedVal.negate();
 
   TempDtorContext TrueContext(
-      bothKnownTrue(Context.KnownExecuted, ConditionVal));
+      bothKnownTrue(Context.KnownExecuted, ConditionVal),
+      Context.TrackExpiringMTEs);
   VisitForTemporaryDtors(E->getTrueExpr(), ExternallyDestructed, TrueContext);
   CFGBlock *TrueBlock = Block;
 
   Block = ConditionBlock;
   Succ = ConditionSucc;
-  TempDtorContext FalseContext(
-      bothKnownTrue(Context.KnownExecuted, NegatedVal));
+  TempDtorContext FalseContext(bothKnownTrue(Context.KnownExecuted, 
NegatedVal),
+                               Context.TrackExpiringMTEs);
   VisitForTemporaryDtors(E->getFalseExpr(), ExternallyDestructed, 
FalseContext);
 
   if (TrueContext.TerminatorExpr && FalseContext.TerminatorExpr) {
@@ -5308,6 +5355,11 @@ CFGBlock 
*CFGBuilder::VisitConditionalOperatorForTemporaryDtors(
   } else {
     InsertTempDtorDecisionBlock(FalseContext);
   }
+  if (Context.TrackExpiringMTEs) {
+    Context.CollectedMTEs.append(TrueContext.CollectedMTEs);
+    Context.CollectedMTEs.append(FalseContext.CollectedMTEs);
+  }
+
   return Block;
 }
 
@@ -5989,6 +6041,13 @@ static void print_elem(raw_ostream &OS, 
StmtPrinterHelper &Helper,
     OS << " (Lifetime ends)";
     break;
 
+  case CFGElement::Kind::FullExprCleanup:
+    OS << "(FullExprCleanup collected "
+       << std::to_string(
+              E.castAs<CFGFullExprCleanup>().getExpiringMTEs().size())
+       << " MTEs)";
+    break;
+
   case CFGElement::Kind::LoopExit:
     OS << E.castAs<CFGLoopExit>().getLoopStmt()->getStmtClassName()
        << " (LoopExit)";
diff --git a/clang/lib/Analysis/PathDiagnostic.cpp 
b/clang/lib/Analysis/PathDiagnostic.cpp
index e42731b93bfb2..5e387e086a5a7 100644
--- a/clang/lib/Analysis/PathDiagnostic.cpp
+++ b/clang/lib/Analysis/PathDiagnostic.cpp
@@ -564,6 +564,7 @@ getLocationForCaller(const StackFrameContext *SFC,
   case CFGElement::CleanupFunction:
     llvm_unreachable("not yet implemented!");
   case CFGElement::LifetimeEnds:
+  case CFGElement::FullExprCleanup:
   case CFGElement::LoopExit:
     llvm_unreachable("CFGElement kind should not be on callsite!");
   }

>From 5cc1973c118ea08159b197836c79af5551354b94 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <[email protected]>
Date: Wed, 28 Jan 2026 03:12:45 +1000
Subject: [PATCH 2/2] Fixed solver related issues by no-oping new CFGElement

---
 clang/lib/Analysis/CFG.cpp                      | 1 +
 clang/lib/StaticAnalyzer/Core/CoreEngine.cpp    | 6 ++++++
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp    | 1 +
 clang/lib/StaticAnalyzer/Core/SymbolManager.cpp | 2 ++
 4 files changed, 10 insertions(+)

diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 03b3fd7efe89e..ca47c644228f4 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -5477,6 +5477,7 @@ CFGImplicitDtor::getDestructorDecl(ASTContext 
&astContext) const {
     case CFGElement::CXXRecordTypedCall:
     case CFGElement::ScopeBegin:
     case CFGElement::ScopeEnd:
+    case CFGElement::FullExprCleanup:
     case CFGElement::CleanupFunction:
       llvm_unreachable("getDestructorDecl should only be used with "
                        "ImplicitDtors");
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index 95a843ee87571..49835a0224171 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -524,6 +524,12 @@ void CoreEngine::HandlePostStmt(const CFGBlock *B, 
unsigned StmtIdx,
   assert(B);
   assert(!B->empty());
 
+  // We no-op by skipping any FullExprCleanup
+  while (StmtIdx < B->size() &&
+         (*B)[StmtIdx].getKind() == CFGElement::FullExprCleanup) {
+    StmtIdx++;
+  }
+
   if (StmtIdx == B->size())
     HandleBlockExit(B, Pred);
   else {
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index a6a96b594fe85..da176ebd20bb4 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -995,6 +995,7 @@ void ExprEngine::processCFGElement(const CFGElement E, 
ExplodedNode *Pred,
       return;
     case CFGElement::LifetimeEnds:
     case CFGElement::CleanupFunction:
+    case CFGElement::FullExprCleanup:
     case CFGElement::ScopeBegin:
     case CFGElement::ScopeEnd:
       return;
diff --git a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp 
b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
index d03f47fa301e6..35fbe4d60c3f2 100644
--- a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
@@ -115,6 +115,8 @@ const Stmt *SymbolConjured::getStmt() const {
     return Elem->castAs<CFGTemporaryDtor>().getBindTemporaryExpr();
   case CFGElement::CleanupFunction:
     return nullptr;
+  case CFGElement::FullExprCleanup:
+    return nullptr;
   }
   return nullptr;
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to