[clang] [LifetimeSafety] Overhaul CFG and analysis to also work with trivially destructed temporary objects (PR #177985)

Abhinav Pradeep via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 12 17:59:11 PST 2026


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

>From ab336db2b56f79ddd82d07c1424dd237022f27cf Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Tue, 27 Jan 2026 01:20:55 +1000
Subject: [PATCH 1/9] 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 e675935d9230a..b4b733bb4d42e 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 8001a67a5e158..e71662879bc33 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,
@@ -1824,8 +1837,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);
     }
   }
 
@@ -2073,6 +2089,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
@@ -3121,8 +3154,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);
     }
   }
 
@@ -4932,13 +4968,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);
@@ -5125,6 +5165,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.
@@ -5216,10 +5258,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;
   }
 
@@ -5298,14 +5344,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) {
@@ -5316,6 +5363,11 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors(
   } else {
     InsertTempDtorDecisionBlock(FalseContext);
   }
+  if (Context.TrackExpiringMTEs) {
+    Context.CollectedMTEs.append(TrueContext.CollectedMTEs);
+    Context.CollectedMTEs.append(FalseContext.CollectedMTEs);
+  }
+
   return Block;
 }
 
@@ -5997,6 +6049,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 7473d0c6ea533f86239e89bc934329bafb3de52a Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Wed, 28 Jan 2026 03:12:45 +1000
Subject: [PATCH 2/9] 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 e71662879bc33..fd970aafd408c 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -5485,6 +5485,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 857b2f0689e05..f3842998c3541 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -521,6 +521,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 545d37764eca4..3a45a38516807 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;
 }

>From 737bda3e55be1da8225ef9fee311e86c020e92e3 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Tue, 3 Feb 2026 00:07:14 +1000
Subject: [PATCH 3/9] Fixed tests to reflect new CFG

---
 .../Analysis/auto-obj-dtors-cfg-output.cpp    | 172 +++++++++---------
 clang/test/Analysis/cfg-rich-constructors.cpp | 155 +++++++++-------
 clang/test/Analysis/cfg-rich-constructors.mm  |   8 +-
 clang/test/Analysis/cfg.cpp                   |   1 +
 .../test/Analysis/missing-bind-temporary.cpp  |  15 +-
 .../Analysis/temp-obj-dtors-cfg-output.cpp    | 166 ++++++++++-------
 6 files changed, 293 insertions(+), 224 deletions(-)

diff --git a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
index 96b9a5508cc08..4790469850107 100644
--- a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
+++ b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
@@ -188,50 +188,52 @@ void test_aggregate_lifetime_extension() {
 // CXX11-NEXT:    14: {[B1.13]}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
 // CXX11-NEXT:    15: C c = {{[{][{]}}A(), A(){{[}][}]}};
-// CXX11-NEXT:    16: ~A() (Temporary object destructor)
+// CXX11-NEXT:    16: (FullExprCleanup collected 2 MTEs)
 // CXX11-NEXT:    17: ~A() (Temporary object destructor)
-// CXX11-WARNINGS-NEXT:    18: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    18: A() (CXXConstructExpr, [B1.19], [B1.21], A)
-// CXX11-NEXT:    19: [B1.18] (BindTemporary)
-// CXX11-NEXT:    20: [B1.19] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    21: [B1.20]
-// CXX11-NEXT:    22: [B1.21] (CXXConstructExpr, const A)
-// CXX11-WARNINGS-NEXT:    23: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    23: A() (CXXConstructExpr, [B1.24], [B1.26], A)
-// CXX11-NEXT:    24: [B1.23] (BindTemporary)
-// CXX11-NEXT:    25: [B1.24] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    26: [B1.25]
-// CXX11-NEXT:    27: [B1.26] (CXXConstructExpr, const A)
+// CXX11-NEXT:    18: ~A() (Temporary object destructor)
+// CXX11-WARNINGS-NEXT:    19: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    19: A() (CXXConstructExpr, [B1.20], [B1.22], A)
+// CXX11-NEXT:    20: [B1.19] (BindTemporary)
+// CXX11-NEXT:    21: [B1.20] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    22: [B1.21]
+// CXX11-NEXT:    23: [B1.22] (CXXConstructExpr, const A)
+// CXX11-WARNINGS-NEXT:    24: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    24: A() (CXXConstructExpr, [B1.25], [B1.27], A)
+// CXX11-NEXT:    25: [B1.24] (BindTemporary)
+// CXX11-NEXT:    26: [B1.25] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    27: [B1.26]
+// CXX11-NEXT:    28: [B1.27] (CXXConstructExpr, const A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CXX11-NEXT:    28: {[B1.19], [B1.24]}
-// CXX11-NEXT:    29: [B1.28] (BindTemporary)
-// CXX11-NEXT:    30: [B1.29]
-// CXX11-NEXT:    31: {[B1.30]}
-// CXX11-WARNINGS-NEXT:    32: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    32: A() (CXXConstructExpr, [B1.33], [B1.35], A)
-// CXX11-NEXT:    33: [B1.32] (BindTemporary)
-// CXX11-NEXT:    34: [B1.33] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    35: [B1.34]
-// CXX11-NEXT:    36: [B1.35] (CXXConstructExpr, const A)
-// CXX11-WARNINGS-NEXT:    37: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    37: A() (CXXConstructExpr, [B1.38], [B1.40], A)
-// CXX11-NEXT:    38: [B1.37] (BindTemporary)
-// CXX11-NEXT:    39: [B1.38] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    40: [B1.39]
-// CXX11-NEXT:    41: [B1.40] (CXXConstructExpr, const A)
+// CXX11-NEXT:    29: {[B1.20], [B1.25]}
+// CXX11-NEXT:    30: [B1.29] (BindTemporary)
+// CXX11-NEXT:    31: [B1.30]
+// CXX11-NEXT:    32: {[B1.31]}
+// CXX11-WARNINGS-NEXT:    33: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    33: A() (CXXConstructExpr, [B1.34], [B1.36], A)
+// CXX11-NEXT:    34: [B1.33] (BindTemporary)
+// CXX11-NEXT:    35: [B1.34] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    36: [B1.35]
+// CXX11-NEXT:    37: [B1.36] (CXXConstructExpr, const A)
+// CXX11-WARNINGS-NEXT:    38: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    38: A() (CXXConstructExpr, [B1.39], [B1.41], A)
+// CXX11-NEXT:    39: [B1.38] (BindTemporary)
+// CXX11-NEXT:    40: [B1.39] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    41: [B1.40]
+// CXX11-NEXT:    42: [B1.41] (CXXConstructExpr, const A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CXX11-NEXT:    42: {[B1.33], [B1.38]}
-// CXX11-NEXT:    43: [B1.42] (BindTemporary)
-// CXX11-NEXT:    44: [B1.43]
-// CXX11-NEXT:    45: {[B1.44]}
+// CXX11-NEXT:    43: {[B1.34], [B1.39]}
+// CXX11-NEXT:    44: [B1.43] (BindTemporary)
+// CXX11-NEXT:    45: [B1.44]
+// CXX11-NEXT:    46: {[B1.45]}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CXX11-NEXT:    46: {{[{][{]}}[B1.30]}, {[B1.44]{{[}][}]}}
+// CXX11-NEXT:    47: {{[{][{]}}[B1.31]}, {[B1.45]{{[}][}]}}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CXX11-NEXT:    47: C cc[2] = {{[{][{][{]}}A(), A(){{[}][}]}}, {{[{][{]}}A(), A(){{[}][}][}]}};
-// CXX11-NEXT:    48: ~A() (Temporary object destructor)
-// CXX11-NEXT:    49: ~A() (Temporary object destructor)
+// CXX11-NEXT:    48: C cc[2] = {{[{][{][{]}}A(), A(){{[}][}]}}, {{[{][{]}}A(), A(){{[}][}][}]}};
+// CXX11-NEXT:    49: (FullExprCleanup collected 4 MTEs)
 // CXX11-NEXT:    50: ~A() (Temporary object destructor)
 // CXX11-NEXT:    51: ~A() (Temporary object destructor)
+// CXX11-NEXT:    52: ~A() (Temporary object destructor)
+// CXX11-NEXT:    53: ~A() (Temporary object destructor)
 // CXX11-NEXT:     Preds (1): B2
 // CXX11-NEXT:     Succs (1): B0
 // CXX11:        [B0 (EXIT)]
@@ -277,65 +279,67 @@ void test_aggregate_array_lifetime_extension() {
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
 // CHECK-NEXT:    17: {[B1.2], {[B1.7], [B1.12]}}
 // CHECK-NEXT:    18: D d = {A(), {A(), A()}};
-// CHECK-NEXT:    19: ~A() (Temporary object destructor)
+// CHECK-NEXT:    19: (FullExprCleanup collected 3 MTEs)
 // CHECK-NEXT:    20: ~A() (Temporary object destructor)
 // CHECK-NEXT:    21: ~A() (Temporary object destructor)
-// WARNINGS-NEXT:    22: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    22: A() (CXXConstructExpr, [B1.23], [B1.25], A)
-// CHECK-NEXT:    23: [B1.22] (BindTemporary)
-// CHECK-NEXT:    24: [B1.23] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    25: [B1.24]
-// CHECK-NEXT:    26: [B1.25] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    27: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    27: A() (CXXConstructExpr, [B1.28], [B1.30], A)
-// CHECK-NEXT:    28: [B1.27] (BindTemporary)
-// CHECK-NEXT:    29: [B1.28] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    30: [B1.29]
-// CHECK-NEXT:    31: [B1.30] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    32: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    32: A() (CXXConstructExpr, [B1.33], [B1.35], A)
-// CHECK-NEXT:    33: [B1.32] (BindTemporary)
-// CHECK-NEXT:    34: [B1.33] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    35: [B1.34]
-// CHECK-NEXT:    36: [B1.35] (CXXConstructExpr, A)
+// CHECK-NEXT:    22: ~A() (Temporary object destructor)
+// WARNINGS-NEXT:    23: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    23: A() (CXXConstructExpr, [B1.24], [B1.26], A)
+// CHECK-NEXT:    24: [B1.23] (BindTemporary)
+// CHECK-NEXT:    25: [B1.24] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    26: [B1.25]
+// CHECK-NEXT:    27: [B1.26] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    28: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    28: A() (CXXConstructExpr, [B1.29], [B1.31], A)
+// CHECK-NEXT:    29: [B1.28] (BindTemporary)
+// CHECK-NEXT:    30: [B1.29] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    31: [B1.30]
+// CHECK-NEXT:    32: [B1.31] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    33: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    33: A() (CXXConstructExpr, [B1.34], [B1.36], A)
+// CHECK-NEXT:    34: [B1.33] (BindTemporary)
+// CHECK-NEXT:    35: [B1.34] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    36: [B1.35]
+// CHECK-NEXT:    37: [B1.36] (CXXConstructExpr, A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    37: {[B1.28], [B1.33]}
+// CHECK-NEXT:    38: {[B1.29], [B1.34]}
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    38: {[B1.23], {[B1.28], [B1.33]}}
-// WARNINGS-NEXT:    39: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    39: A() (CXXConstructExpr, [B1.40], [B1.42], A)
-// CHECK-NEXT:    40: [B1.39] (BindTemporary)
-// CHECK-NEXT:    41: [B1.40] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    42: [B1.41]
-// CHECK-NEXT:    43: [B1.42] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    44: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    44: A() (CXXConstructExpr, [B1.45], [B1.47], A)
-// CHECK-NEXT:    45: [B1.44] (BindTemporary)
-// CHECK-NEXT:    46: [B1.45] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    47: [B1.46]
-// CHECK-NEXT:    48: [B1.47] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    49: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    49: A() (CXXConstructExpr, [B1.50], [B1.52], A)
-// CHECK-NEXT:    50: [B1.49] (BindTemporary)
-// CHECK-NEXT:    51: [B1.50] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    52: [B1.51]
-// CHECK-NEXT:    53: [B1.52] (CXXConstructExpr, A)
+// CHECK-NEXT:    39: {[B1.24], {[B1.29], [B1.34]}}
+// WARNINGS-NEXT:    40: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    40: A() (CXXConstructExpr, [B1.41], [B1.43], A)
+// CHECK-NEXT:    41: [B1.40] (BindTemporary)
+// CHECK-NEXT:    42: [B1.41] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    43: [B1.42]
+// CHECK-NEXT:    44: [B1.43] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    45: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    45: A() (CXXConstructExpr, [B1.46], [B1.48], A)
+// CHECK-NEXT:    46: [B1.45] (BindTemporary)
+// CHECK-NEXT:    47: [B1.46] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    48: [B1.47]
+// CHECK-NEXT:    49: [B1.48] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    50: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    50: A() (CXXConstructExpr, [B1.51], [B1.53], A)
+// CHECK-NEXT:    51: [B1.50] (BindTemporary)
+// CHECK-NEXT:    52: [B1.51] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    53: [B1.52]
+// CHECK-NEXT:    54: [B1.53] (CXXConstructExpr, A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    54: {[B1.45], [B1.50]}
+// CHECK-NEXT:    55: {[B1.46], [B1.51]}
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    55: {[B1.40], {[B1.45], [B1.50]}}
+// CHECK-NEXT:    56: {[B1.41], {[B1.46], [B1.51]}}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CHECK-NEXT:    56: {{[{][{]}}[B1.23], {[B1.28], [B1.33]{{[}][}]}}, {[B1.40], {[B1.45], [B1.50]{{[}][}][}]}}
+// CHECK-NEXT:    57: {{[{][{]}}[B1.24], {[B1.29], [B1.34]{{[}][}]}}, {[B1.41], {[B1.46], [B1.51]{{[}][}][}]}}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CHECK-NEXT:    57: D dd[2] = {{[{][{]}}A(), {A(), A(){{[}][}]}}, {A(), {A(), A(){{[}][}][}]}};
-// CHECK-NEXT:    58: ~A() (Temporary object destructor)
-// CHECK-NEXT:    59: ~A() (Temporary object destructor)
+// CHECK-NEXT:    58: D dd[2] = {{[{][{]}}A(), {A(), A(){{[}][}]}}, {A(), {A(), A(){{[}][}][}]}};
+// CHECK-NEXT:    59: (FullExprCleanup collected 6 MTEs)
 // CHECK-NEXT:    60: ~A() (Temporary object destructor)
 // CHECK-NEXT:    61: ~A() (Temporary object destructor)
 // CHECK-NEXT:    62: ~A() (Temporary object destructor)
 // CHECK-NEXT:    63: ~A() (Temporary object destructor)
-// CHECK-NEXT:    64: [B1.57].~D[2]() (Implicit destructor)
-// CHECK-NEXT:    65: [B1.18].~D() (Implicit destructor)
+// CHECK-NEXT:    64: ~A() (Temporary object destructor)
+// CHECK-NEXT:    65: ~A() (Temporary object destructor)
+// CHECK-NEXT:    66: [B1.58].~D[2]() (Implicit destructor)
+// CHECK-NEXT:    67: [B1.18].~D() (Implicit destructor)
 // CHECK-NEXT:     Preds (1): B2
 // CHECK-NEXT:     Succs (1): B0
 // CHECK:        [B0 (EXIT)]
diff --git a/clang/test/Analysis/cfg-rich-constructors.cpp b/clang/test/Analysis/cfg-rich-constructors.cpp
index aea983f1f74da..c91eec9fda871 100644
--- a/clang/test/Analysis/cfg-rich-constructors.cpp
+++ b/clang/test/Analysis/cfg-rich-constructors.cpp
@@ -253,20 +253,22 @@ class D: public C {
 // CHECK-NEXT:     4: [B1.3]
 // CHECK-NEXT:     5: [B1.4] (CXXConstructExpr, C([B1.4]) (Base initializer), C)
 // CHECK-NEXT:     6: C([B1.5]) (Base initializer)
-// CHECK-NEXT:     7: CFGNewAllocator(C *)
-// CHECK-NEXT:     8: C::get
-// CHECK-NEXT:     9: [B1.8] (ImplicitCastExpr, FunctionToPointerDecay, C (*)(void))
-// CXX11-ELIDE-NEXT:    10: [B1.9]() (CXXRecordTypedCall, [B1.11], [B1.12])
-// CXX11-NOELIDE-NEXT:    10: [B1.9]() (CXXRecordTypedCall, [B1.11])
-// CXX11-NEXT:    11: [B1.10]
-// CXX11-NEXT:    12: [B1.11] (CXXConstructExpr, [B1.13], C)
-// CXX11-NEXT:    13: new C([B1.12])
-// CXX11-NEXT:    14: [B1.13] (CXXConstructExpr, c1([B1.13]) (Member initializer), C)
-// CXX11-NEXT:    15: c1([B1.14]) (Member initializer)
-// CXX17-NEXT:    10: [B1.9]()
-// CXX17-NEXT:    11: new C([B1.10])
-// CXX17-NEXT:    12: [B1.11] (CXXConstructExpr, c1([B1.11]) (Member initializer), C)
-// CXX17-NEXT:    13: c1([B1.12]) (Member initializer)
+// CHECK-NEXT:     7: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:     8: CFGNewAllocator(C *)
+// CHECK-NEXT:     9: C::get
+// CHECK-NEXT:     10: [B1.9] (ImplicitCastExpr, FunctionToPointerDecay, C (*)(void))
+// CXX11-ELIDE-NEXT:    11: [B1.10]() (CXXRecordTypedCall, [B1.12], [B1.13])
+// CXX11-NOELIDE-NEXT:    11: [B1.10]() (CXXRecordTypedCall, [B1.12])
+// CXX11-NEXT:    12: [B1.11]
+// CXX11-NEXT:    13: [B1.12] (CXXConstructExpr, [B1.14], C)
+// CXX11-NEXT:    14: new C([B1.13])
+// CXX11-NEXT:    15: [B1.14] (CXXConstructExpr, c1([B1.14]) (Member initializer), C)
+// CXX11-NEXT:    16: c1([B1.15]) (Member initializer)
+// CXX11-NEXT:    17: (FullExprCleanup collected 1 MTEs)
+// CXX17-NEXT:    11: [B1.10]()
+// CXX17-NEXT:    12: new C([B1.11])
+// CXX17-NEXT:    13: [B1.12] (CXXConstructExpr, c1([B1.12]) (Member initializer), C)
+// CXX17-NEXT:    14: c1([B1.13]) (Member initializer)
   D(double): C(C::get()), c1(new C(C::get())) {}
 };
 
@@ -292,7 +294,8 @@ class F {
 // CXX11-NEXT:     6: [B1.5]
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, e([B1.6]) (Member initializer), E)
 // CXX11-NEXT:     8: e([B1.7]) (Member initializer)
-// CXX11-NEXT:     9: ~E() (Temporary object destructor)
+// CXX11-NEXT:     9: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     10: ~E() (Temporary object destructor)
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, e([B1.4]) (Member initializer), [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: e([B1.4]) (Member initializer)
@@ -345,8 +348,9 @@ C returnBracesWithMultipleItems() {
 // CXX11-ELIDE:    1: C() (CXXConstructExpr, [B1.2], [B1.3], C)
 // CXX11-NOELIDE:  1: C() (CXXConstructExpr, [B1.2], C)
 // CXX11-NEXT:     2: [B1.1]
-// CXX11-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.4], C)
-// CXX11-NEXT:     4: return [B1.3];
+// CXX11-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.5], C)
+// CXX11-NEXT:     4: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     5: return [B1.3];
 // CXX17:          1: C() (CXXConstructExpr, [B1.2], C)
 // CXX17-NEXT:     2: return [B1.1];
 C returnTemporary() {
@@ -361,8 +365,9 @@ C returnTemporary() {
 // CXX17-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.5], C)
 // CHECK-NEXT:     4: C([B1.3]) (CXXFunctionalCastExpr, ConstructorConversion, C)
 // CXX11-NEXT:     5: [B1.4]
-// CXX11-NEXT:     6: [B1.5] (CXXConstructExpr, [B1.7], C)
-// CXX11-NEXT:     7: return [B1.6];
+// CXX11-NEXT:     6: [B1.5] (CXXConstructExpr, [B1.8], C)
+// CXX11-NEXT:     7: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     8: return [B1.6];
 // CXX17-NEXT:     5: return [B1.4];
 
 C returnTemporaryWithArgument() {
@@ -375,8 +380,9 @@ C returnTemporaryWithArgument() {
 // CXX11-ELIDE-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.4], [B1.5])
 // CXX11-NOELIDE-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.4])
 // CXX11-NEXT:     4: [B1.3]
-// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.6], C)
-// CXX11-NEXT:     6: return [B1.5];
+// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.7], C)
+// CXX11-NEXT:     6: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     7: return [B1.5];
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.4])
 // CXX17-NEXT:     4: return [B1.3];
 C returnTemporaryConstructedByFunction() {
@@ -393,8 +399,9 @@ C returnTemporaryConstructedByFunction() {
 // CXX11-NOELIDE-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.7], C)
 // CXX11-NEXT:     6: C([B1.5]) (CXXFunctionalCastExpr, ConstructorConversion, C)
 // CXX11-NEXT:     7: [B1.6]
-// CXX11-NEXT:     8: [B1.7] (CXXConstructExpr, [B1.9], C)
-// CXX11-NEXT:     9: return [B1.8];
+// CXX11-NEXT:     8: [B1.7] (CXXConstructExpr, [B1.10], C)
+// CXX11-NEXT:     9: (FullExprCleanup collected 2 MTEs)
+// CXX11-NEXT:     10: return [B1.8];
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.5])
 // CXX17-NEXT:     4: C([B1.3]) (CXXFunctionalCastExpr, NoOp, C)
 // CXX17-NEXT:     5: return [B1.4];
@@ -418,9 +425,10 @@ class D {
 // CXX11-NEXT:     2: [B1.1] (BindTemporary)
 // CXX11-NEXT:     3: [B1.2] (ImplicitCastExpr, NoOp, const D)
 // CXX11-NEXT:     4: [B1.3]
-// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.7], D)
-// CXX11-NEXT:     6: ~D() (Temporary object destructor)
-// CXX11-NEXT:     7: return [B1.5];
+// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.8], D)
+// CXX11-NEXT:     6: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     7: ~D() (Temporary object destructor)
+// CXX11-NEXT:     8: return [B1.5];
 // CXX17:          1: D() (CXXConstructExpr, [B1.3], [B1.2], D)
 // CXX17-NEXT:     2: [B1.1] (BindTemporary)
 // CXX17-NEXT:     3: return [B1.2];
@@ -438,8 +446,9 @@ D returnTemporary() {
 // CXX11-NEXT:     6: [B1.5]
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, [B1.8], D)
 // CXX11-NEXT:     8: D d = returnTemporary();
-// CXX11-NEXT:     9: ~D() (Temporary object destructor)
-// CXX11-NEXT:    10: [B1.8].~D() (Implicit destructor)
+// CXX11-NEXT:     9: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     10: ~D() (Temporary object destructor)
+// CXX11-NEXT:    11: [B1.8].~D() (Implicit destructor)
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.5], [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: D d = returnTemporary();
@@ -466,6 +475,7 @@ void simpleTemporary() {
 // CHECK-NEXT:     4: [B2.3].operator bool
 // CHECK-NEXT:     5: [B2.3]
 // CHECK-NEXT:     6: [B2.5] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CHECK-NEXT:     7: (FullExprCleanup collected 1 MTEs)
 // CHECK-NEXT:     T: if [B2.6]
 void temporaryInCondition() {
   if (C());
@@ -477,12 +487,13 @@ void temporaryInCondition() {
 // CXX11-NEXT:     2: [B2.1]
 // CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.4], C)
 // CXX11-NEXT:     4: C c = C();
-// CXX11-NEXT:     5: c
-// CXX11-NEXT:     6: [B2.5] (ImplicitCastExpr, NoOp, const class C)
-// CXX11-NEXT:     7: [B2.6].operator bool
-// CXX11-NEXT:     8: [B2.6]
-// CXX11-NEXT:     9: [B2.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11-NEXT:     T: if [B2.9]
+// CXX11-NEXT:     5: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     6: c
+// CXX11-NEXT:     7: [B2.6] (ImplicitCastExpr, NoOp, const class C)
+// CXX11-NEXT:     8: [B2.7].operator bool
+// CXX11-NEXT:     9: [B2.7]
+// CXX11-NEXT:     10: [B2.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11-NEXT:     T: if [B2.10]
 // CXX17:          1: C() (CXXConstructExpr, [B2.2], C)
 // CXX17-NEXT:     2: C c = C();
 // CXX17-NEXT:     3: c
@@ -501,14 +512,15 @@ void temporaryInConditionVariable() {
 // CXX11-ELIDE-NEXT:     1: C() (CXXConstructExpr, [B2.2], [B2.3], C)
 // CXX11-NOELIDE-NEXT:     1: C() (CXXConstructExpr, [B2.2], C)
 // CXX11-NEXT:     2: [B2.1]
-// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.4], C)
-// CXX11-NEXT:     4: C c2 = C();
-// CXX11-NEXT:     5: c2
-// CXX11-NEXT:     6: [B2.5] (ImplicitCastExpr, NoOp, const class C)
-// CXX11-NEXT:     7: [B2.6].operator bool
-// CXX11-NEXT:     8: [B2.6]
-// CXX11-NEXT:     9: [B2.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11-NEXT:     T: for (...; [B2.9]; )
+// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.5], C)
+// CXX11-NEXT:     4: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     5: C c2 = C();
+// CXX11-NEXT:     6: c2
+// CXX11-NEXT:     7: [B2.6] (ImplicitCastExpr, NoOp, const class C)
+// CXX11-NEXT:     8: [B2.7].operator bool
+// CXX11-NEXT:     9: [B2.7]
+// CXX11-NEXT:     10: [B2.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11-NEXT:     T: for (...; [B2.10]; )
 // CXX17-NEXT:     1: C() (CXXConstructExpr, [B2.2], C)
 // CXX17-NEXT:     2: C c2 = C();
 // CXX17-NEXT:     3: c2
@@ -534,14 +546,15 @@ void temporaryInForLoopConditionVariable() {
 // CXX11-ELIDE:          1: C() (CXXConstructExpr, [B2.2], [B2.3], C)
 // CXX11-NOELIDE:          1: C() (CXXConstructExpr, [B2.2], C)
 // CXX11-NEXT:     2: [B2.1]
-// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.4], C)
-// CXX11-NEXT:     4: C c = C();
-// CXX11-NEXT:     5: c
-// CXX11-NEXT:     6: [B2.5] (ImplicitCastExpr, NoOp, const class C)
-// CXX11-NEXT:     7: [B2.6].operator bool
-// CXX11-NEXT:     8: [B2.6]
-// CXX11-NEXT:     9: [B2.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11-NEXT:     T: while [B2.9]
+// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.5], C)
+// CXX11-NEXT:     4: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:     5: C c = C();
+// CXX11-NEXT:     6: c
+// CXX11-NEXT:     7: [B2.6] (ImplicitCastExpr, NoOp, const class C)
+// CXX11-NEXT:     8: [B2.7].operator bool
+// CXX11-NEXT:     9: [B2.7
+// CXX11-NEXT:     10: [B2.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11-NEXT:     T: while [B2.10]
 // CXX17:          1: C() (CXXConstructExpr, [B2.2], C)
 // CXX17-NEXT:     2: C c = C();
 // CXX17-NEXT:     3: c
@@ -585,7 +598,8 @@ void simpleTemporary() {
 // CHECK-NEXT:     5: [B2.4].operator bool
 // CHECK-NEXT:     6: [B2.4]
 // CHECK-NEXT:     7: [B2.6] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK-NEXT:     8: ~D() (Temporary object destructor)
+// CHECK-NEXT:     8: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:     9: ~D() (Temporary object destructor)
 // CHECK-NEXT:     T: if [B2.7]
 void temporaryInCondition() {
   if (D());
@@ -625,6 +639,7 @@ void referenceVariableWithInitializer() {
 // CXX11-NEXT:     2: [B4.1] (ImplicitCastExpr, NoOp, const D)
 // CXX11-NEXT:     3: [B4.2]
 // CXX11-NEXT:     4: const D &d = coin ? D::get() : D(0);
+// CXX11-NEXT:     5: (FullExprCleanup collected 2 MTEs)
 // CXX11-NEXT:     T: (Temp Dtor) [B6.3]
 // CXX11:        [B5]
 // CXX11-NEXT:     1: D::get
@@ -737,14 +752,16 @@ class B {
 // CXX11-NEXT:     8: [B1.7]
 // CXX11-NEXT:     9: [B1.8] (CXXConstructExpr, [B1.10], B)
 // CXX11-NEXT:    10: B b = A();
-// CXX11-NEXT:    11: ~B() (Temporary object destructor)
-// CXX11-NEXT:    12: [B1.10].~B() (Implicit destructor)
+// CXX11-NEXT:    11: (FullExprCleanup collected 2 MTEs)
+// CXX11-NEXT:    12: ~B() (Temporary object destructor)
+// CXX11-NEXT:    13: [B1.10].~B() (Implicit destructor)
 // CXX17-NEXT:     2: [B1.1] (ImplicitCastExpr, NoOp, const A)
 // CXX17-NEXT:     3: [B1.2]
 // CXX17-NEXT:     4: [B1.3] (CXXConstructExpr, [B1.6], B)
 // CXX17-NEXT:     5: [B1.4] (ImplicitCastExpr, ConstructorConversion, B)
 // CXX17-NEXT:     6: B b = A();
-// CXX17-NEXT:     7: [B1.6].~B() (Implicit destructor)
+// CXX17-NEXT:     7: (FullExprCleanup collected 1 MTEs)
+// CXX17-NEXT:     8: [B1.6].~B() (Implicit destructor)
 void implicitConstructionConversionFromTemporary() {
   B b = A();
 }
@@ -763,12 +780,14 @@ void implicitConstructionConversionFromTemporary() {
 // CXX11-NEXT:    10: [B1.9]
 // CXX11-NEXT:    11: [B1.10] (CXXConstructExpr, [B1.12], B)
 // CXX11-NEXT:    12: B b = get();
-// CXX11-NEXT:    13: ~B() (Temporary object destructor)
-// CXX11-NEXT:    14: [B1.12].~B() (Implicit destructor)
+// CXX11-NEXT:    13: (FullExprCleanup collected 2 MTEs)
+// CXX11-NEXT:    14: ~B() (Temporary object destructor)
+// CXX11-NEXT:    15: [B1.12].~B() (Implicit destructor)
 // CXX17-NEXT:     6: [B1.5] (CXXConstructExpr, [B1.8], B)
 // CXX17-NEXT:     7: [B1.6] (ImplicitCastExpr, ConstructorConversion, B)
 // CXX17-NEXT:     8: B b = get();
-// CXX17-NEXT:     9: [B1.8].~B() (Implicit destructor)
+// CXX17-NEXT:     9: (FullExprCleanup collected 1 MTEs)
+// CXX17-NEXT:    10: [B1.8].~B() (Implicit destructor)
 void implicitConstructionConversionFromFunctionValue() {
   B b = get();
 }
@@ -782,7 +801,8 @@ void implicitConstructionConversionFromFunctionValue() {
 // CHECK-NEXT:     6: [B1.5] (ImplicitCastExpr, NoOp, const B)
 // CHECK-NEXT:     7: [B1.6]
 // CHECK-NEXT:     8: const B &b = A();
-// CHECK-NEXT:     9: [B1.8].~B() (Implicit destructor)
+// CHECK-NEXT:     9: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:     10: [B1.8].~B() (Implicit destructor)
 void implicitConstructionConversionFromTemporaryWithLifetimeExtension() {
   const B &b = A();
 }
@@ -798,7 +818,8 @@ void implicitConstructionConversionFromTemporaryWithLifetimeExtension() {
 // CHECK-NEXT:     8: [B1.7] (ImplicitCastExpr, NoOp, const B)
 // CHECK-NEXT:     9: [B1.8]
 // CHECK-NEXT:    10: const B &b = get();
-// CHECK-NEXT:    11: [B1.10].~B() (Implicit destructor)
+// CHECK-NEXT:    11: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:    12: [B1.10].~B() (Implicit destructor)
 void implicitConstructionConversionFromFunctionValueWithLifetimeExtension() {
   const B &b = get(); // no-crash
 }
@@ -854,8 +875,9 @@ void passArgument() {
 // CXX11-NEXT:    10: [B1.9] (CXXConstructExpr, [B1.11], [B1.12]+1, D)
 // CXX11-NEXT:    11: [B1.10] (BindTemporary)
 // CXX11-NEXT:    12: [B1.2]([B1.5], [B1.11])
-// CXX11-NEXT:    13: ~D() (Temporary object destructor)
+// CXX11-NEXT:    13: (FullExprCleanup collected 2 MTEs)
 // CXX11-NEXT:    14: ~D() (Temporary object destructor)
+// CXX11-NEXT:    15: ~D() (Temporary object destructor)
 // CXX17-NEXT:     3: C() (CXXConstructExpr, [B1.6]+0, C)
 // CXX17-NEXT:     4: D() (CXXConstructExpr, [B1.5], [B1.6]+1, D)
 // CXX17-NEXT:     5: [B1.4] (BindTemporary)
@@ -887,8 +909,9 @@ void passArgumentByReference() {
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, [B1.8], [B1.9]+0, D)
 // CXX11-NEXT:     8: [B1.7] (BindTemporary)
 // CXX11-NEXT:     9: [B1.2]([B1.8])
-// CXX11-NEXT:    10: ~D() (Temporary object destructor)
+// CXX11-NEXT:    10: (FullExprCleanup collected 1 MTEs)
 // CXX11-NEXT:    11: ~D() (Temporary object destructor)
+// CXX11-NEXT:    12: ~D() (Temporary object destructor)
 // CXX17-NEXT:     3: D() (CXXConstructExpr, [B1.4], [B1.5]+0, D)
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: [B1.2]([B1.4])
@@ -905,7 +928,8 @@ void passArgumentWithDestructor() {
 // CHECK-NEXT:     5: [B1.4] (ImplicitCastExpr, NoOp, const D)
 // CHECK-NEXT:     6: [B1.5]
 // CHECK-NEXT:     7: [B1.2]([B1.6])
-// CHECK-NEXT:     8: ~D() (Temporary object destructor)
+// CHECK-NEXT:     8: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:     9: ~D() (Temporary object destructor)
 void passArgumentWithDestructorByReference() {
   useDByReference(D());
 }
@@ -924,8 +948,9 @@ void passArgumentWithDestructorByReference() {
 // CXX11-NEXT:     9: [B1.8]
 // CXX11-NEXT:    10: [B1.9] (CXXConstructExpr, [B1.11], E)
 // CXX11-NEXT:    11: E e = E(D());
-// CXX11-NEXT:    12: ~D() (Temporary object destructor)
+// CXX11-NEXT:    12: (FullExprCleanup collected 2 MTEs)
 // CXX11-NEXT:    13: ~D() (Temporary object destructor)
+// CXX11-NEXT:    14: ~D() (Temporary object destructor)
 // CXX17:          1: D() (CXXConstructExpr, [B1.2], [B1.3]+0, D)
 // CXX17-NEXT:     2: [B1.1] (BindTemporary)
 // CXX17-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.5], E)
@@ -953,10 +978,11 @@ void passArgumentIntoAnotherConstructor() {
 // CXX11-NEXT:    11: [B1.10] (CXXConstructExpr, [B1.12], [B1.13]+1, D)
 // CXX11-NEXT:    12: [B1.11] (BindTemporary)
 // CXX11-NEXT:    13: E([B1.6], [B1.12]) (CXXConstructExpr, E)
-// CXX11-NEXT:    14: ~D() (Temporary object destructor)
+// CXX11-NEXT:    14: (FullExprCleanup collected 2 MTEs)
 // CXX11-NEXT:    15: ~D() (Temporary object destructor)
 // CXX11-NEXT:    16: ~D() (Temporary object destructor)
 // CXX11-NEXT:    17: ~D() (Temporary object destructor)
+// CXX11-NEXT:    18: ~D() (Temporary object destructor)
 // CXX17:          1: D() (CXXConstructExpr, [B1.2], [B1.5]+0, D)
 // CXX17-NEXT:     2: [B1.1] (BindTemporary)
 // CXX17-NEXT:     3: D() (CXXConstructExpr, [B1.4], [B1.5]+1, D)
@@ -1034,6 +1060,7 @@ int variadic(...);
 
 // This code is quite exotic, so let's not test the CFG for it,
 // but only make sure we don't crash.
+// CHECK-LABEL: void testCrashOnVariadicArgument()
 void testCrashOnVariadicArgument() {
   C c(variadic(0 ? c : 0)); // no-crash
 }
diff --git a/clang/test/Analysis/cfg-rich-constructors.mm b/clang/test/Analysis/cfg-rich-constructors.mm
index f048f061e9fba..1b73c7ce55f76 100644
--- a/clang/test/Analysis/cfg-rich-constructors.mm
+++ b/clang/test/Analysis/cfg-rich-constructors.mm
@@ -30,8 +30,9 @@ -(D) bar;
 // CXX11-NEXT:     8: [B1.7] (BindTemporary)
 // Double brackets trigger FileCheck variables, escape.
 // CXX11-NEXT:     9: {{\[}}[B1.2] foo:[B1.8]]
-// CXX11-NEXT:    10: ~D() (Temporary object destructor)
+// CXX11-NEXT:    10: (FullExprCleanup collected 1 MTEs)
 // CXX11-NEXT:    11: ~D() (Temporary object destructor)
+// CXX11-NEXT:    12: ~D() (Temporary object destructor)
 // CXX17-NEXT:     3: D() (CXXConstructExpr, [B1.4], [B1.5]+0, D)
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // Double brackets trigger FileCheck variables, escape.
@@ -53,8 +54,9 @@ void passArgumentIntoMessage(E *e) {
 // CXX11-NEXT:     6: [B1.5]
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, [B1.8], D)
 // CXX11-NEXT:     8: D d = [e bar];
-// CXX11-NEXT:     9: ~D() (Temporary object destructor)
-// CXX11-NEXT:    10: [B1.8].~D() (Implicit destructor)
+// CXX11-NEXT:     9: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:    10: ~D() (Temporary object destructor)
+// CXX11-NEXT:    11: [B1.8].~D() (Implicit destructor)
 // Double brackets trigger FileCheck variables, escape.
 // CXX17-NEXT:     3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.5], [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
diff --git a/clang/test/Analysis/cfg.cpp b/clang/test/Analysis/cfg.cpp
index 2a88b73d27756..9a0a60110876b 100644
--- a/clang/test/Analysis/cfg.cpp
+++ b/clang/test/Analysis/cfg.cpp
@@ -390,6 +390,7 @@ void test_lifetime_extended_temporaries() {
     3;
   }
   // CHECK: LifetimeExtend(4)
+  // CHECK-NEXT: (FullExprCleanup collected 2 MTEs)
   // CHECK-NEXT: ~LifetimeExtend()
   // CHECK-NEXT: ~LifetimeExtend()
   // CHECK-NEXT: : 4
diff --git a/clang/test/Analysis/missing-bind-temporary.cpp b/clang/test/Analysis/missing-bind-temporary.cpp
index 3d1af469dc01c..78bcb570a3ce5 100644
--- a/clang/test/Analysis/missing-bind-temporary.cpp
+++ b/clang/test/Analysis/missing-bind-temporary.cpp
@@ -28,8 +28,9 @@ class B {
 // CHECK-NEXT:    7: [B1.6] (BindTemporary)
 // CHECK-NEXT:    8: [B1.7]
 // CHECK-NEXT:    9: [B1.5] = [B1.8] (OperatorCall)
-// CHECK-NEXT:   10: ~B() (Temporary object destructor)
-// CHECK-NEXT:   11: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:   10: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:   11: ~B() (Temporary object destructor)
+// CHECK-NEXT:   12: [B1.2].~B() (Implicit destructor)
 void foo(int) {
   B i;
   i = {};
@@ -65,8 +66,9 @@ class B {
 // CHECK-NEXT:    7: [B1.6] (BindTemporary)
 // CHECK-NEXT:    8: [B1.7]
 // CHECK-NEXT:    9: [B1.5] = [B1.8] (OperatorCall)
-// CHECK-NEXT:    10: ~B() (Temporary object destructor)
-// CHECK-NEXT:    11: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:    10: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:    11: ~B() (Temporary object destructor)
+// CHECK-NEXT:    12: [B1.2].~B() (Implicit destructor)
 template <typename T> void foo(T) {
   B i;
   i = {};
@@ -107,8 +109,9 @@ class B {
 // CHECK-NEXT:    8: [B1.7] (BindTemporary)
 // CHECK-NEXT:    9: [B1.8]
 // CHECK-NEXT:   10: [B1.5] = [B1.9] (OperatorCall)
-// CHECK-NEXT:   11: ~B() (Temporary object destructor)
-// CHECK-NEXT:   12: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:   11: (FullExprCleanup collected 1 MTEs)
+// CHECK-NEXT:   12: ~B() (Temporary object destructor)
+// CHECK-NEXT:   13: [B1.2].~B() (Implicit destructor)
 template <typename T> void foo(T) {
   B i;
   i = {};
diff --git a/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp b/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp
index b496d261e92f4..22807741fdae1 100644
--- a/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp
+++ b/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp
@@ -240,9 +240,10 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B1.2] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     4: [B1.3]
 // WARNINGS:     5: [B1.4] (CXXConstructExpr, A)
-// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.7], A)
-// CHECK:     6: ~A() (Temporary object destructor)
-// CHECK:     7: return [B1.5];
+// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.8], A)
+// CHECK:     6: (FullExprCleanup collected 1 MTEs)
+// CHECK:     7: ~A() (Temporary object destructor)
+// CHECK:     8: return [B1.5];
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -300,9 +301,10 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B1.2] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     4: [B1.3]
 // WARNINGS:     5: [B1.4] (CXXConstructExpr, A)
-// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.7], A)
-// CHECK:     6: ~A() (Temporary object destructor)
-// CHECK:     7: return [B1.5];
+// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.8], A)
+// CHECK:     6: (FullExprCleanup collected 1 MTEs)
+// CHECK:     7: ~A() (Temporary object destructor)
+// CHECK:     8: return [B1.5];
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -328,31 +330,33 @@ const C &bar3(bool coin) {
 // CHECK:    14: int([B1.13]) (CXXFunctionalCastExpr, NoOp, int)
 // CHECK:    15: [B1.7] + [B1.14]
 // CHECK:    16: int a = int(A()) + int(B());
-// CHECK:    17: ~B() (Temporary object destructor)
-// CHECK:    18: ~A() (Temporary object destructor)
-// CHECK:    19: foo
-// CHECK:    20: [B1.19] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(int))
-// WARNINGS:    21: A() (CXXConstructExpr, A)
-// ANALYZER:    21: A() (CXXConstructExpr, [B1.22], [B1.23], A)
-// CHECK:    22: [B1.21] (BindTemporary)
-// CHECK:    23: [B1.22]
-// CHECK:    24: [B1.23].operator int
-// CHECK:    25: [B1.23]
-// CHECK:    26: [B1.25] (ImplicitCastExpr, UserDefinedConversion, int)
-// CHECK:    27: int([B1.26]) (CXXFunctionalCastExpr, NoOp, int)
-// WARNINGS:    28: B() (CXXConstructExpr, B)
-// ANALYZER:    28: B() (CXXConstructExpr, [B1.29], [B1.30], B)
-// CHECK:    29: [B1.28] (BindTemporary)
-// CHECK:    30: [B1.29]
-// CHECK:    31: [B1.30].operator int
-// CHECK:    32: [B1.30]
-// CHECK:    33: [B1.32] (ImplicitCastExpr, UserDefinedConversion, int)
-// CHECK:    34: int([B1.33]) (CXXFunctionalCastExpr, NoOp, int)
-// CHECK:    35: [B1.27] + [B1.34]
-// CHECK:    36: [B1.20]([B1.35])
-// CHECK:    37: ~B() (Temporary object destructor)
-// CHECK:    38: ~A() (Temporary object destructor)
-// CHECK:    39: int b;
+// CHECK:    17: (FullExprCleanup collected 2 MTEs)
+// CHECK:    18: ~B() (Temporary object destructor)
+// CHECK:    19: ~A() (Temporary object destructor)
+// CHECK:    20: foo
+// CHECK:    21: [B1.20] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(int))
+// WARNINGS:    22: A() (CXXConstructExpr, A)
+// ANALYZER:    22: A() (CXXConstructExpr, [B1.23], [B1.24], A)
+// CHECK:    23: [B1.22] (BindTemporary)
+// CHECK:    24: [B1.23]
+// CHECK:    25: [B1.24].operator int
+// CHECK:    26: [B1.24]
+// CHECK:    27: [B1.26] (ImplicitCastExpr, UserDefinedConversion, int)
+// CHECK:    28: int([B1.27]) (CXXFunctionalCastExpr, NoOp, int)
+// WARNINGS:    29: B() (CXXConstructExpr, B)
+// ANALYZER:    29: B() (CXXConstructExpr, [B1.30], [B1.31], B)
+// CHECK:    30: [B1.29] (BindTemporary)
+// CHECK:    31: [B1.30]
+// CHECK:    32: [B1.31].operator int
+// CHECK:    33: [B1.31]
+// CHECK:    34: [B1.33] (ImplicitCastExpr, UserDefinedConversion, int)
+// CHECK:    35: int([B1.34]) (CXXFunctionalCastExpr, NoOp, int)
+// CHECK:    36: [B1.28] + [B1.35]
+// CHECK:    37: [B1.21]([B1.36])
+// CHECK:    38: (FullExprCleanup collected 2 MTEs)
+// CHECK:    39: ~B() (Temporary object destructor)
+// CHECK:    40: ~A() (Temporary object destructor)
+// CHECK:    41: int b;
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -371,6 +375,7 @@ const C &bar3(bool coin) {
 // CHECK:   [B3]
 // CHECK:     1: [B5.9] && [B4.6]
 // CHECK:     2: [B5.3]([B3.1])
+// CHECK:     3: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B4.2]
 // CHECK:     Preds (2): B4 B5
 // CHECK:     Succs (2): B2 B1
@@ -405,6 +410,7 @@ const C &bar3(bool coin) {
 // CHECK:   [B7]
 // CHECK:     1: [B9.6] && [B8.6]
 // CHECK:     2: bool a = A() && B();
+// CHECK:     3: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B8.2]
 // CHECK:     Preds (2): B8 B9
 // CHECK:     Succs (2): B6 B5
@@ -479,6 +485,7 @@ const C &bar3(bool coin) {
 // CHECK:   [B7]
 // CHECK:     1: [B9.6] || [B8.6]
 // CHECK:     2: bool a = A() || B();
+// CHECK:     3: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B8.2]
 // CHECK:     Preds (2): B8 B9
 // CHECK:     Succs (2): B6 B5
@@ -535,7 +542,8 @@ const C &bar3(bool coin) {
 // CHECK:     5: [B4.4].operator bool
 // CHECK:     6: [B4.4]
 // CHECK:     7: [B4.6] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK:     8: ~B() (Temporary object destructor)
+// CHECK:     8: (FullExprCleanup collected 1 MTEs)
+// CHECK:     9: ~B() (Temporary object destructor)
 // CHECK:     T: if [B4.7]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -558,6 +566,7 @@ const C &bar3(bool coin) {
 // WARNINGS:     4: [B7.3] (CXXConstructExpr, A)
 // ANALYZER:     4: [B7.3] (CXXConstructExpr, [B7.5], A)
 // CHECK:     5: A a = B() ? A() : A(B());
+// CHECK:     6: (FullExprCleanup collected 6 MTEs)
 // CHECK:     T: (Temp Dtor) [B9.2]
 // CHECK:     Preds (2): B8 B9
 // CHECK:     Succs (2): B6 B5
@@ -671,7 +680,8 @@ const C &bar3(bool coin) {
 // CHECK:     4: [B3.3].operator bool
 // CHECK:     5: [B3.3]
 // CHECK:     6: [B3.5] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK:     7: ~C() (Temporary object destructor)
+// CHECK:     7: (FullExprCleanup collected 1 MTEs)
+// CHECK:     8: ~C() (Temporary object destructor)
 // CHECK:     T: if [B3.6]
 // CHECK:     Preds (1): B4
 // CHECK:     Succs (2): B2 B1
@@ -703,12 +713,13 @@ const C &bar3(bool coin) {
 // WARNINGS:     5: [B4.4] (CXXConstructExpr, C)
 // ANALYZER:     5: [B4.4] (CXXConstructExpr, [B4.6], C)
 // CHECK:     6: C c = C();
-// CHECK:     7: ~C() (Temporary object destructor)
-// CHECK:     8: c
-// CHECK:     9: [B4.8].operator bool
-// CHECK:    10: [B4.8]
-// CHECK:    11: [B4.10] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK:     T: if [B4.11]
+// CHECK:     7: (FullExprCleanup collected 1 MTEs)
+// CHECK:     8: ~C() (Temporary object destructor)
+// CHECK:     9: c
+// CHECK:    10: [B4.9].operator bool
+// CHECK:    11: [B4.9]
+// CHECK:    12: [B4.11] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CHECK:     T: if [B4.12]
 // CHECK:     Preds (1): B5
 // CHECK:     Succs (2): B3 B2
 // CHECK:   [B0 (EXIT)]
@@ -732,6 +743,7 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B3.2].operator bool
 // CHECK:     4: [B3.2]
 // CHECK:     5: [B3.4] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CHECK:     6: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: if [B3.5]
 // CHECK:     Preds (1): B4
 // CHECK:     Succs (2): B2 B1
@@ -757,22 +769,24 @@ const C &bar3(bool coin) {
 // CXX98-WARNINGS:     4: [B3.3] (CXXConstructExpr, D)
 // CXX98-ANALYZER:     4: [B3.3] (CXXConstructExpr, [B3.5], D)
 // CXX98:     5: D d = D();
-// CXX98:     6: d
-// CXX98:     7: [B3.6].operator bool
-// CXX98:     8: [B3.6]
-// CXX98:     9: [B3.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX98:     T: if [B3.9]
+// CXX98:     6: (FullExprCleanup collected 1 MTEs)
+// CXX98:     7: d
+// CXX98:     8: [B3.7].operator bool
+// CXX98:     9: [B3.7]
+// CXX98:    10: [B3.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX98:     T: if [B3.10]
 // CXX11-WARNINGS:     1: D() (CXXConstructExpr, D)
 // CXX11-ANALYZER:     1: D() (CXXConstructExpr, [B3.2], [B3.3], D)
 // CXX11:     2: [B3.1]
 // CXX11-WARNINGS:     3: [B3.2] (CXXConstructExpr, D)
 // CXX11-ANALYZER:     3: [B3.2] (CXXConstructExpr, [B3.4], D)
 // CXX11:     4: D d = D();
-// CXX11:     5: d
-// CXX11:     6: [B3.5].operator bool
-// CXX11:     7: [B3.5]
-// CXX11:     8: [B3.7] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11:     T: if [B3.8]
+// CXX11:     5: (FullExprCleanup collected 1 MTEs)
+// CXX11:     6: d
+// CXX11:     7: [B3.6].operator bool
+// CXX11:     8: [B3.6]
+// CXX11:     9: [B3.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11:     T: if [B3.9]
 // CHECK:     Preds (1): B4
 // CHECK:     Succs (2): B2 B1
 // CHECK:   [B0 (EXIT)]
@@ -802,6 +816,7 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B4.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B4.2]
 // CHECK:     4: [B7.3]([B4.3])
+// CHECK:     5: (FullExprCleanup collected 6 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.2]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -867,6 +882,7 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B10.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B10.2]
 // CHECK:     4: const A &a = B() ? A() : A(B());
+// CHECK:     5: (FullExprCleanup collected 5 MTEs)
 // CHECK:     T: (Temp Dtor) [B12.2]
 // CHECK:     Preds (2): B11 B12
 // CHECK:     Succs (2): B9 B8
@@ -940,6 +956,8 @@ const C &bar3(bool coin) {
 // WARNINGS:     4: [B4.3] (CXXConstructExpr, A)
 // ANALYZER:     4: [B4.3] (CXXConstructExpr, [B4.5], A)
 // CHECK:     5: A a = A() ?: A();
+// CXX98:     6: (FullExprCleanup collected 4 MTEs)
+// CXX11:     6: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.2]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -1002,6 +1020,8 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B4.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B4.2]
 // CHECK:     4: [B7.2]([B4.3])
+// CXX98:     5: (FullExprCleanup collected 4 MTEs)
+// CXX11:     5: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.2]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -1052,6 +1072,8 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B9.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B9.2]
 // CHECK:     4: const A &a = A() ?: A();
+// CXX98:     5: (FullExprCleanup collected 3 MTEs)
+// CXX11:     5: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B11.2]
 // CHECK:     Preds (2): B10 B11
 // CHECK:     Succs (2): B8 B7
@@ -1103,9 +1125,10 @@ const C &bar3(bool coin) {
 // WARNINGS:     5: [B1.4] (CXXConstructExpr, A)
 // ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.6], A)
 // CHECK:     6: A a = A();
-// CHECK:     7: ~A() (Temporary object destructor)
-// CHECK:     8: int b;
-// CHECK:     9: [B1.6].~A() (Implicit destructor)
+// CHECK:     7: (FullExprCleanup collected 1 MTEs)
+// CHECK:     8: ~A() (Temporary object destructor)
+// CHECK:     9: int b;
+// CHECK:    10: [B1.6].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1127,9 +1150,10 @@ const C &bar3(bool coin) {
 // CHECK:    10: [B1.9] (ImplicitCastExpr, NoOp, const A)
 // CHECK:    11: [B1.10]
 // CHECK:    12: [B1.7]([B1.11])
-// CHECK:    13: ~A() (Temporary object destructor)
-// CHECK:    14: int b;
-// CHECK:    15: [B1.5].~A() (Implicit destructor)
+// CHECK:    13: (FullExprCleanup collected 1 MTEs)
+// CHECK:    14: ~A() (Temporary object destructor)
+// CHECK:    15: int b;
+// CHECK:    16: [B1.5].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1147,9 +1171,10 @@ const C &bar3(bool coin) {
 // WARNINGS:     7: [B1.6] (CXXConstructExpr, A)
 // ANALYZER:     7: [B1.6] (CXXConstructExpr, [B1.8], A)
 // CHECK:     8: A a = A::make();
-// CHECK:     9: ~A() (Temporary object destructor)
-// CHECK:    10: int b;
-// CHECK:    11: [B1.8].~A() (Implicit destructor)
+// CHECK:     9: (FullExprCleanup collected 1 MTEs)
+// CHECK:    10: ~A() (Temporary object destructor)
+// CHECK:    11: int b;
+// CHECK:    12: [B1.8].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1175,9 +1200,10 @@ const C &bar3(bool coin) {
 // CHECK:    14: [B1.13] (ImplicitCastExpr, NoOp, const A)
 // CHECK:    15: [B1.14]
 // CHECK:    16: [B1.9]([B1.15])
-// CHECK:    17: ~A() (Temporary object destructor)
-// CHECK:    18: int b;
-// CHECK:    19: [B1.7].~A() (Implicit destructor)
+// CHECK:    17: (FullExprCleanup collected 1 MTEs)
+// CHECK:    18: ~A() (Temporary object destructor)
+// CHECK:    19: int b;
+// CHECK:    20: [B1.7].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1195,8 +1221,9 @@ const C &bar3(bool coin) {
 // CHECK:     7: [B1.6] (ImplicitCastExpr, UserDefinedConversion, int)
 // CHECK:     8: a
 // CHECK:     9: [B1.8] = [B1.7]
-// CHECK:    10: ~A() (Temporary object destructor)
-// CHECK:    11: int b;
+// CHECK:    10: (FullExprCleanup collected 1 MTEs)
+// CHECK:    11: ~A() (Temporary object destructor)
+// CHECK:    12: int b;
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1222,9 +1249,10 @@ const C &bar3(bool coin) {
 // CHECK:    14: int([B1.13]) (CXXFunctionalCastExpr, NoOp, int)
 // CHECK:    15: [B1.7] + [B1.14]
 // CHECK:    16: a([B1.15]) (Member initializer)
-// CHECK:    17: ~B() (Temporary object destructor)
-// CHECK:    18: ~A() (Temporary object destructor)
-// CHECK:    19: b(/*implicit*/(int)0) (Member initializer)
+// CHECK:    17: (FullExprCleanup collected 2 MTEs)
+// CHECK:    18: ~B() (Temporary object destructor)
+// CHECK:    19: ~A() (Temporary object destructor)
+// CHECK:    20: b(/*implicit*/(int)0) (Member initializer)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1243,7 +1271,8 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B2.2] (BindTemporary)
 // CHECK:     [[MEMBER:[45]]]: [B2.{{[34]}}].f
 // CHECK:     {{[56]}}: [B2.[[MEMBER]]]()
-// CHECK:     {{[67]}}: ~NoReturn() (Temporary object destructor)
+// CHECK:     7: (FullExprCleanup collected 1 MTEs)
+// CHECK:     {{[78]}}: ~NoReturn() (Temporary object destructor)
 // CHECK:     Preds (1): B3
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1288,6 +1317,7 @@ const C &bar3(bool coin) {
 // CHECK:     Succs (1): B0
 // CHECK:   [B5]
 // CHECK:     1: [B7.3] || [B6.7]
+// CHECK:     2: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.4]
 // CHECK:     Preds (2): B6 B7
 // CHECK:     Succs (2): B4 B3
@@ -1339,6 +1369,7 @@ const C &bar3(bool coin) {
 // CHECK:     Succs (1): B0
 // CHECK:   [B5]
 // CHECK:     1: [B8.3] || [B7.3] || [B6.7]
+// CHECK:     2: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.4]
 // CHECK:     Preds (3): B6 B7 B8
 // CHECK:     Succs (2): B4 B3
@@ -1397,6 +1428,7 @@ const C &bar3(bool coin) {
 // CHECK:     Succs (1): B0
 // CHECK:   [B5]
 // CHECK:     1: [B8.3] || [B7.2] || [B6.7]
+// CHECK:     2: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.4]
 // CHECK:     Preds (3): B6 B7 B8
 // CHECK:     Succs (2): B4(Unreachable) B3

>From 055ce18b040ad04cafe0f538f50e9928b826857e Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Wed, 4 Feb 2026 00:42:20 +1000
Subject: [PATCH 4/9] Set up analysis to consume the new CFGFullExprCleanup

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  3 +-
 .../LifetimeSafety/FactsGenerator.cpp         | 40 +++++++------------
 .../Sema/warn-lifetime-analysis-nocfg.cpp     | 21 +++++-----
 clang/test/Sema/warn-lifetime-safety.cpp      | 11 ++---
 4 files changed, 34 insertions(+), 41 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 8b45337bee218..4f649d5a19d84 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -61,7 +61,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
 
   void handleCXXCtorInitializer(const CXXCtorInitializer *CII);
   void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
-  void handleTemporaryDtor(const CFGTemporaryDtor &TemporaryDtor);
+
+  void handleFullExprCleanup(const CFGFullExprCleanup &FullExprCleanup);
 
   void handleExitBlock();
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index fb859eeb856af..4e42e674ac4b4 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -9,12 +9,16 @@
 #include <cassert>
 #include <string>
 
+#include "clang/AST/ExprCXX.h"
 #include "clang/AST/OperationKinds.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
+#include "clang/Analysis/CFG.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/TimeProfiler.h"
@@ -81,17 +85,6 @@ static const PathLoan *createLoan(FactManager &FactMgr,
   return FactMgr.getLoanMgr().createLoan<PathLoan>(Path, MTE);
 }
 
-/// Try to find a CXXBindTemporaryExpr that descends from MTE, stripping away
-/// any implicit casts.
-/// \param MTE MaterializeTemporaryExpr whose descendants we are interested in.
-/// \return Pointer to descendant CXXBindTemporaryExpr or nullptr when not
-/// found.
-static const CXXBindTemporaryExpr *
-getChildBinding(const MaterializeTemporaryExpr *MTE) {
-  const Expr *Child = MTE->getSubExpr()->IgnoreImpCasts();
-  return dyn_cast<CXXBindTemporaryExpr>(Child);
-}
-
 void FactsGenerator::run() {
   llvm::TimeTraceScope TimeProfile("FactGenerator");
   const CFG &Cfg = *AC.getCFG();
@@ -114,9 +107,9 @@ void FactsGenerator::run() {
       else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
                    Element.getAs<CFGLifetimeEnds>())
         handleLifetimeEnds(*LifetimeEnds);
-      else if (std::optional<CFGTemporaryDtor> TemporaryDtor =
-                   Element.getAs<CFGTemporaryDtor>())
-        handleTemporaryDtor(*TemporaryDtor);
+      else if (std::optional<CFGFullExprCleanup> FullExprCleanup =
+                   Element.getAs<CFGFullExprCleanup>())
+        handleFullExprCleanup(*FullExprCleanup);
     }
     if (Block == &Cfg.getExit())
       handleExitBlock();
@@ -437,7 +430,7 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
   OriginList *RValMTEList = getRValueOrigins(MTE, MTEList);
   flow(RValMTEList, SubExprList, /*Kill=*/true);
   OriginID OuterMTEID = MTEList->getOuterOriginID();
-  if (getChildBinding(MTE)) {
+  if (MTE->getStorageDuration() == SD_FullExpression) {
     // Issue a loan to MTE for the storage location represented by MTE.
     const Loan *L = createLoan(FactMgr, MTE);
     CurrentBlockFacts.push_back(
@@ -446,7 +439,6 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
 }
 
 void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
-  /// TODO: Handle loans to temporaries.
   const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
   if (!LifetimeEndsVD)
     return;
@@ -469,12 +461,10 @@ void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
   }
 }
 
-void FactsGenerator::handleTemporaryDtor(
-    const CFGTemporaryDtor &TemporaryDtor) {
-  const CXXBindTemporaryExpr *ExpiringBTE =
-      TemporaryDtor.getBindTemporaryExpr();
-  if (!ExpiringBTE)
-    return;
+void FactsGenerator::handleFullExprCleanup(
+    const CFGFullExprCleanup &FullExprCleanup) {
+  ArrayRef<const MaterializeTemporaryExpr *> CollectedMTEs =
+      FullExprCleanup.getExpiringMTEs();
   // Iterate through all loans to see if any expire.
   for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
     if (const auto *PL = dyn_cast<PathLoan>(Loan)) {
@@ -484,9 +474,9 @@ void FactsGenerator::handleTemporaryDtor(
       const MaterializeTemporaryExpr *Path = AP.getAsMaterializeTemporaryExpr();
       if (!Path)
         continue;
-      if (ExpiringBTE == getChildBinding(Path)) {
-        CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
-            PL->getID(), TemporaryDtor.getBindTemporaryExpr()->getEndLoc()));
+      if (is_contained(CollectedMTEs, Path)) {
+        CurrentBlockFacts.push_back(
+            FactMgr.createFact<ExpireFact>(PL->getID(), Path->getEndLoc()));
       }
     }
   }
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index a58b446fe4c07..e06b08543ed6b 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -298,19 +298,19 @@ std::string_view danglingRefToOptionalFromTemp4() {
 void danglingReferenceFromTempOwner() {
   int &&r = *std::optional<int>();          // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
                                             // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
-  // FIXME: Detect this using the CFG-based lifetime analysis.
   //        https://github.com/llvm/llvm-project/issues/175893
-  int &&r2 = *std::optional<int>(5);        // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+  int &&r2 = *std::optional<int>(5);        // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
+                                              // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
 
-  // FIXME: Detect this using the CFG-based lifetime analysis.
   //        https://github.com/llvm/llvm-project/issues/175893
-  int &&r3 = std::optional<int>(5).value(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+  int &&r3 = std::optional<int>(5).value(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
+                                              // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
 
   const int &r4 = std::vector<int>().at(3); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
                                             // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
   int &&r5 = std::vector<int>().at(3);      // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
                                             // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
-  use(r, r2, r3, r4, r5);                   // cfg-note 3 {{later used here}}
+  use(r, r2, r3, r4, r5);                   // cfg-note 5 {{later used here}}
 
   std::string_view sv = *getTempOptStr();  // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
                                            // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
@@ -383,9 +383,9 @@ void handleGslPtrInitsThroughReference2() {
 
 void handleTernaryOperator(bool cond) {
     std::basic_string<char> def;
-    // FIXME: Detect this using the CFG-based lifetime analysis.
-    std::basic_string_view<char> v = cond ? def : ""; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
-    use(v);
+    std::basic_string_view<char> v = cond ? def : ""; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
+                                                      // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+    use(v); // cfg-note {{later used here}}
 }
 
 std::string operator+(std::string_view s1, std::string_view s2);
@@ -849,8 +849,9 @@ class set {
 namespace GH118064{
 
 void test() {
-  auto y = std::set<int>{}.begin(); // expected-warning {{object backing the pointer}}
-  use(y);
+  auto y = std::set<int>{}.begin(); // expected-warning {{object backing the pointer}} \
+  // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+  use(y); // cfg-note {{later used here}}
 }
 } // namespace GH118064
 
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 2976c809e389c..f728f8fec4302 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1103,12 +1103,11 @@ void passing_temporary_to_lifetime_bound_function() {
   use(a); // expected-note {{later used here}}
 }
 
-// FIXME: We expect to be warned of use-after-free at use(a), but this is not the
-// case as current analysis does not handle trivially destructed temporaries.
 void use_trivial_temporary_after_destruction() {
   View a;
-  a = trivially_destructed_temporary();
-  use(a);
+  a = trivially_destructed_temporary(); // expected-warning {{object whose reference is captured does not live long enough}} \
+                expected-note {{destroyed here}}
+  use(a); // expected-note {{later used here}}
 }
 
 namespace GH162834 {
@@ -1454,7 +1453,9 @@ void bar() {
     {
         S s;
         x = s.x(); // expected-warning {{object whose reference is captured does not live long enough}}
-        View y = S().x(); // FIXME: Handle temporaries.
+        View y = S().x(); // expected-warning {{object whose reference is captured does not live long enough}} \
+                             expected-note {{destroyed here}}
+        (void)y; // expected-note {{used here}}
     } // expected-note {{destroyed here}}
     (void)x; // expected-note {{used here}}
 }

>From ca8045b242638905dd9eba9f43e18b1a27c62a8d Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Wed, 4 Feb 2026 20:01:30 +1000
Subject: [PATCH 5/9] Addressed review comments

---
 clang/include/clang/Analysis/CFG.h | 6 +++---
 clang/lib/Analysis/CFG.cpp         | 6 +++---
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h
index b4b733bb4d42e..72b7ac013ccc2 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -315,11 +315,11 @@ class CFGLifetimeEnds : public CFGElement {
 };
 
 class CFGFullExprCleanup : public CFGElement {
-  using MTEVecTy = BumpVector<const MaterializeTemporaryExpr *>;
 
 public:
-  explicit CFGFullExprCleanup(const MTEVecTy *vec)
-      : CFGElement(FullExprCleanup, vec, nullptr) {}
+  using MTEVecTy = BumpVector<const MaterializeTemporaryExpr *>;
+  explicit CFGFullExprCleanup(const MTEVecTy *MTEs)
+      : CFGElement(FullExprCleanup, MTEs, nullptr) {}
 
   ArrayRef<const MaterializeTemporaryExpr *> getExpiringMTEs() const {
     const MTEVecTy *ExpiringMTEs =
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index fd970aafd408c..430e0ed541a2e 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -2092,14 +2092,14 @@ void CFGBuilder::addScopeChangesHandling(LocalScope::const_iterator SrcPos,
 void CFGBuilder::addFullExprCleanupMarker(TempDtorContext &Context) {
   assert(Context.TrackExpiringMTEs);
 
-  using MTEVecTy = BumpVector<const MaterializeTemporaryExpr *>;
-  MTEVecTy *ExpiringMTEs = nullptr;
+  CFGFullExprCleanup::MTEVecTy *ExpiringMTEs = nullptr;
   BumpVectorContext &BVC = cfg->getBumpVectorContext();
 
   size_t NumCollected = Context.CollectedMTEs.size();
   if (NumCollected > 0) {
     autoCreateBlock();
-    ExpiringMTEs = new (cfg->getAllocator()) MTEVecTy(BVC, NumCollected);
+    ExpiringMTEs = new (cfg->getAllocator())
+        CFGFullExprCleanup::MTEVecTy(BVC, NumCollected);
     for (const MaterializeTemporaryExpr *MTE : Context.CollectedMTEs)
       ExpiringMTEs->push_back(MTE, BVC);
     Block->appendFullExprCleanup(ExpiringMTEs, BVC);

>From a56e25eaa5348e0b03b2c9b3e829b970c2800b67 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Tue, 10 Feb 2026 18:45:21 +1000
Subject: [PATCH 6/9] CFGFullExprCleanup is treated like a lifetime marker
 instead of a dtor

---
 clang/lib/Analysis/CFG.cpp | 105 ++++++++++++++++---------------------
 1 file changed, 46 insertions(+), 59 deletions(-)

diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 430e0ed541a2e..5d70932f7347b 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -699,9 +699,6 @@ 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
@@ -720,14 +717,11 @@ class CFGBuilder {
     }
 
     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;
@@ -736,19 +730,19 @@ class CFGBuilder {
 
   // Visitors to walk an AST and generate destructors of temporaries in
   // full expression.
-  CFGBlock *VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
+  CFGBlock *VisitForTemporaries(Stmt *E, bool ExternallyDestructed,
                                    TempDtorContext &Context);
-  CFGBlock *VisitChildrenForTemporaryDtors(Stmt *E,  bool ExternallyDestructed,
+  CFGBlock *VisitChildrenForTemporaries(Stmt *E,  bool ExternallyDestructed,
                                            TempDtorContext &Context);
-  CFGBlock *VisitBinaryOperatorForTemporaryDtors(BinaryOperator *E,
+  CFGBlock *VisitBinaryOperatorForTemporaries(BinaryOperator *E,
                                                  bool ExternallyDestructed,
                                                  TempDtorContext &Context);
   CFGBlock *VisitCXXBindTemporaryExprForTemporaryDtors(
       CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context);
-  CFGBlock *VisitConditionalOperatorForTemporaryDtors(
+  CFGBlock *VisitConditionalOperatorForTemporaries(
       AbstractConditionalOperator *E, bool ExternallyDestructed,
       TempDtorContext &Context);
-  void InsertTempDtorDecisionBlock(const TempDtorContext &Context,
+  void InsertTempDecisionBlock(const TempDtorContext &Context,
                                    CFGBlock *FalseSucc = nullptr);
 
   // NYS == Not Yet Supported
@@ -1837,8 +1831,7 @@ 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(),
+      VisitForTemporaries(cast<ExprWithCleanups>(Init)->getSubExpr(),
                              /*ExternallyDestructed=*/false, Context);
 
       addFullExprCleanupMarker(Context);
@@ -2090,7 +2083,6 @@ void CFGBuilder::addScopeChangesHandling(LocalScope::const_iterator SrcPos,
 }
 
 void CFGBuilder::addFullExprCleanupMarker(TempDtorContext &Context) {
-  assert(Context.TrackExpiringMTEs);
 
   CFGFullExprCleanup::MTEVecTy *ExpiringMTEs = nullptr;
   BumpVectorContext &BVC = cfg->getBumpVectorContext();
@@ -3154,8 +3146,7 @@ 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(),
+      VisitForTemporaries(cast<ExprWithCleanups>(Init)->getSubExpr(),
                              /*ExternallyDestructed=*/true, Context);
 
       addFullExprCleanupMarker(Context);
@@ -4974,8 +4965,7 @@ CFGBlock *CFGBuilder::VisitExprWithCleanups(ExprWithCleanups *E,
     // If adding implicit destructors visit the full expression for adding
     // destructors of temporaries.
     TempDtorContext Context;
-    Context.TrackExpiringMTEs = true;
-    VisitForTemporaryDtors(E->getSubExpr(), ExternallyDestructed, Context);
+    VisitForTemporaries(E->getSubExpr(), ExternallyDestructed, Context);
 
     addFullExprCleanupMarker(Context);
 
@@ -5114,9 +5104,8 @@ CFGBlock *CFGBuilder::VisitIndirectGotoStmt(IndirectGotoStmt *I) {
   return addStmt(I->getTarget());
 }
 
-CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
+CFGBlock *CFGBuilder::VisitForTemporaries(Stmt *E, bool ExternallyDestructed,
                                              TempDtorContext &Context) {
-  assert(BuildOpts.AddImplicitDtors && BuildOpts.AddTemporaryDtors);
 
 tryAgain:
   if (!E) {
@@ -5125,13 +5114,13 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
   }
   switch (E->getStmtClass()) {
     default:
-      return VisitChildrenForTemporaryDtors(E, false, Context);
+      return VisitChildrenForTemporaries(E, false, Context);
 
     case Stmt::InitListExprClass:
-      return VisitChildrenForTemporaryDtors(E, ExternallyDestructed, Context);
+      return VisitChildrenForTemporaries(E, ExternallyDestructed, Context);
 
     case Stmt::BinaryOperatorClass:
-      return VisitBinaryOperatorForTemporaryDtors(cast<BinaryOperator>(E),
+      return VisitBinaryOperatorForTemporaries(cast<BinaryOperator>(E),
                                                   ExternallyDestructed,
                                                   Context);
 
@@ -5141,7 +5130,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
 
     case Stmt::BinaryConditionalOperatorClass:
     case Stmt::ConditionalOperatorClass:
-      return VisitConditionalOperatorForTemporaryDtors(
+      return VisitConditionalOperatorForTemporaries(
           cast<AbstractConditionalOperator>(E), ExternallyDestructed, Context);
 
     case Stmt::ImplicitCastExprClass:
@@ -5165,7 +5154,7 @@ 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)
+      if (BuildOpts.AddLifetime && !ExternallyDestructed)
         Context.track(MTE);
       SmallVector<const Expr *, 2> CommaLHSs;
       SmallVector<SubobjectAdjustment, 2> Adjustments;
@@ -5176,7 +5165,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
               ->skipRValueSubobjectAdjustments(CommaLHSs, Adjustments));
       // Visit the skipped comma operator left-hand sides for other temporaries.
       for (const Expr *CommaLHS : CommaLHSs) {
-        VisitForTemporaryDtors(const_cast<Expr *>(CommaLHS),
+        VisitForTemporaries(const_cast<Expr *>(CommaLHS),
                                /*ExternallyDestructed=*/false, Context);
       }
       goto tryAgain;
@@ -5194,7 +5183,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
       CFGBlock *B = Block;
       for (Expr *Init : LE->capture_inits()) {
         if (Init) {
-          if (CFGBlock *R = VisitForTemporaryDtors(
+          if (CFGBlock *R = VisitForTemporaries(
                   Init, /*ExternallyDestructed=*/true, Context))
             B = R;
         }
@@ -5217,7 +5206,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed,
   }
 }
 
-CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E,
+CFGBlock *CFGBuilder::VisitChildrenForTemporaries(Stmt *E,
                                                      bool ExternallyDestructed,
                                                      TempDtorContext &Context) {
   if (isa<LambdaExpr>(E)) {
@@ -5225,31 +5214,31 @@ CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E,
     return Block;
   }
 
-  // When visiting children for destructors we want to visit them in reverse
+  // When visiting children for destructors or lifetime markers we want to visit them in reverse
   // order that they will appear in the CFG.  Because the CFG is built
   // bottom-up, this means we visit them in their natural order, which
   // reverses them in the CFG.
   CFGBlock *B = Block;
   for (Stmt *Child : E->children())
     if (Child)
-      if (CFGBlock *R = VisitForTemporaryDtors(Child, ExternallyDestructed, Context))
+      if (CFGBlock *R = VisitForTemporaries(Child, ExternallyDestructed, Context))
         B = R;
 
   return B;
 }
 
-CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors(
+CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaries(
     BinaryOperator *E, bool ExternallyDestructed, TempDtorContext &Context) {
   if (E->isCommaOp()) {
     // For the comma operator, the LHS expression is evaluated before the RHS
     // expression, so prepend temporary destructors for the LHS first.
-    CFGBlock *LHSBlock = VisitForTemporaryDtors(E->getLHS(), false, Context);
-    CFGBlock *RHSBlock = VisitForTemporaryDtors(E->getRHS(), ExternallyDestructed, Context);
+    CFGBlock *LHSBlock = VisitForTemporaries(E->getLHS(), false, Context);
+    CFGBlock *RHSBlock = VisitForTemporaries(E->getRHS(), ExternallyDestructed, Context);
     return RHSBlock ? RHSBlock : LHSBlock;
   }
 
   if (E->isLogicalOp()) {
-    VisitForTemporaryDtors(E->getLHS(), false, Context);
+    VisitForTemporaries(E->getLHS(), false, Context);
     TryResult RHSExecuted = tryEvaluateBool(E->getLHS());
     if (RHSExecuted.isKnown() && E->getOpcode() == BO_LOr)
       RHSExecuted.negate();
@@ -5258,12 +5247,11 @@ CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors(
     // executed, thus we add a branch node that depends on the temporary
     // constructor call.
     TempDtorContext RHSContext(
-        bothKnownTrue(Context.KnownExecuted, RHSExecuted),
-        Context.TrackExpiringMTEs);
-    VisitForTemporaryDtors(E->getRHS(), false, RHSContext);
-    InsertTempDtorDecisionBlock(RHSContext);
+        bothKnownTrue(Context.KnownExecuted, RHSExecuted));
+    VisitForTemporaries(E->getRHS(), false, RHSContext);
+    InsertTempDecisionBlock(RHSContext);
 
-    if (Context.TrackExpiringMTEs)
+    if (BuildOpts.AddLifetime)
       Context.CollectedMTEs.append(RHSContext.CollectedMTEs);
 
     return Block;
@@ -5272,21 +5260,23 @@ CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors(
   if (E->isAssignmentOp()) {
     // For assignment operators, the RHS expression is evaluated before the LHS
     // expression, so prepend temporary destructors for the RHS first.
-    CFGBlock *RHSBlock = VisitForTemporaryDtors(E->getRHS(), false, Context);
-    CFGBlock *LHSBlock = VisitForTemporaryDtors(E->getLHS(), false, Context);
+    CFGBlock *RHSBlock = VisitForTemporaries(E->getRHS(), false, Context);
+    CFGBlock *LHSBlock = VisitForTemporaries(E->getLHS(), false, Context);
     return LHSBlock ? LHSBlock : RHSBlock;
   }
 
   // Any other operator is visited normally.
-  return VisitChildrenForTemporaryDtors(E, ExternallyDestructed, Context);
+  return VisitChildrenForTemporaries(E, ExternallyDestructed, Context);
 }
 
 CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors(
     CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context) {
   // First add destructors for temporaries in subexpression.
   // Because VisitCXXBindTemporaryExpr calls setDestructed:
-  CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), true, Context);
-  if (!ExternallyDestructed) {
+  CFGBlock *B = VisitForTemporaries(E->getSubExpr(), true, Context);
+  if (!ExternallyDestructed && 
+    BuildOpts.AddImplicitDtors && 
+    BuildOpts.AddTemporaryDtors) {
     // If lifetime of temporary is not prolonged (by assigning to constant
     // reference) add destructor for it.
 
@@ -5311,13 +5301,12 @@ CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors(
       Context.setDecisionPoint(Succ, E);
     }
     appendTemporaryDtor(Block, E);
-
-    B = Block;
+    return B;
   }
   return B;
 }
 
-void CFGBuilder::InsertTempDtorDecisionBlock(const TempDtorContext &Context,
+void CFGBuilder::InsertTempDecisionBlock(const TempDtorContext &Context,
                                              CFGBlock *FalseSucc) {
   if (!Context.TerminatorExpr) {
     // If no temporary was found, we do not need to insert a decision point.
@@ -5333,10 +5322,10 @@ void CFGBuilder::InsertTempDtorDecisionBlock(const TempDtorContext &Context,
   Block = Decision;
 }
 
-CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors(
+CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaries(
     AbstractConditionalOperator *E, bool ExternallyDestructed,
     TempDtorContext &Context) {
-  VisitForTemporaryDtors(E->getCond(), false, Context);
+  VisitForTemporaries(E->getCond(), false, Context);
   CFGBlock *ConditionBlock = Block;
   CFGBlock *ConditionSucc = Succ;
   TryResult ConditionVal = tryEvaluateBool(E->getCond());
@@ -5344,26 +5333,24 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors(
   if (NegatedVal.isKnown()) NegatedVal.negate();
 
   TempDtorContext TrueContext(
-      bothKnownTrue(Context.KnownExecuted, ConditionVal),
-      Context.TrackExpiringMTEs);
-  VisitForTemporaryDtors(E->getTrueExpr(), ExternallyDestructed, TrueContext);
+      bothKnownTrue(Context.KnownExecuted, ConditionVal));
+  VisitForTemporaries(E->getTrueExpr(), ExternallyDestructed, TrueContext);
   CFGBlock *TrueBlock = Block;
 
   Block = ConditionBlock;
   Succ = ConditionSucc;
-  TempDtorContext FalseContext(bothKnownTrue(Context.KnownExecuted, NegatedVal),
-                               Context.TrackExpiringMTEs);
-  VisitForTemporaryDtors(E->getFalseExpr(), ExternallyDestructed, FalseContext);
+  TempDtorContext FalseContext(bothKnownTrue(Context.KnownExecuted, NegatedVal));
+  VisitForTemporaries(E->getFalseExpr(), ExternallyDestructed, FalseContext);
 
   if (TrueContext.TerminatorExpr && FalseContext.TerminatorExpr) {
-    InsertTempDtorDecisionBlock(FalseContext, TrueBlock);
+    InsertTempDecisionBlock(FalseContext, TrueBlock);
   } else if (TrueContext.TerminatorExpr) {
     Block = TrueBlock;
-    InsertTempDtorDecisionBlock(TrueContext);
+    InsertTempDecisionBlock(TrueContext);
   } else {
-    InsertTempDtorDecisionBlock(FalseContext);
+    InsertTempDecisionBlock(FalseContext);
   }
-  if (Context.TrackExpiringMTEs) {
+  if (BuildOpts.AddLifetime) {
     Context.CollectedMTEs.append(TrueContext.CollectedMTEs);
     Context.CollectedMTEs.append(FalseContext.CollectedMTEs);
   }

>From 6910a64e3da25531162ffa04c9bf9e0dec15f97d Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Tue, 10 Feb 2026 18:45:41 +1000
Subject: [PATCH 7/9] Revert "Fixed tests to reflect new CFG"

These tests check dtors not lifetime markers. As CFGFullExprCleanup is now effectively a lifetime marker and not treated like a dtor, these changes must be reverted.

This reverts commit 737bda3e55be1da8225ef9fee311e86c020e92e3.
---
 .../Analysis/auto-obj-dtors-cfg-output.cpp    | 172 +++++++++---------
 clang/test/Analysis/cfg-rich-constructors.cpp | 155 +++++++---------
 clang/test/Analysis/cfg-rich-constructors.mm  |   8 +-
 clang/test/Analysis/cfg.cpp                   |   1 -
 .../test/Analysis/missing-bind-temporary.cpp  |  15 +-
 .../Analysis/temp-obj-dtors-cfg-output.cpp    | 166 +++++++----------
 6 files changed, 224 insertions(+), 293 deletions(-)

diff --git a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
index 4790469850107..96b9a5508cc08 100644
--- a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
+++ b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
@@ -188,52 +188,50 @@ void test_aggregate_lifetime_extension() {
 // CXX11-NEXT:    14: {[B1.13]}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
 // CXX11-NEXT:    15: C c = {{[{][{]}}A(), A(){{[}][}]}};
-// CXX11-NEXT:    16: (FullExprCleanup collected 2 MTEs)
+// CXX11-NEXT:    16: ~A() (Temporary object destructor)
 // CXX11-NEXT:    17: ~A() (Temporary object destructor)
-// CXX11-NEXT:    18: ~A() (Temporary object destructor)
-// CXX11-WARNINGS-NEXT:    19: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    19: A() (CXXConstructExpr, [B1.20], [B1.22], A)
-// CXX11-NEXT:    20: [B1.19] (BindTemporary)
-// CXX11-NEXT:    21: [B1.20] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    22: [B1.21]
-// CXX11-NEXT:    23: [B1.22] (CXXConstructExpr, const A)
-// CXX11-WARNINGS-NEXT:    24: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    24: A() (CXXConstructExpr, [B1.25], [B1.27], A)
-// CXX11-NEXT:    25: [B1.24] (BindTemporary)
-// CXX11-NEXT:    26: [B1.25] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    27: [B1.26]
-// CXX11-NEXT:    28: [B1.27] (CXXConstructExpr, const A)
+// CXX11-WARNINGS-NEXT:    18: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    18: A() (CXXConstructExpr, [B1.19], [B1.21], A)
+// CXX11-NEXT:    19: [B1.18] (BindTemporary)
+// CXX11-NEXT:    20: [B1.19] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    21: [B1.20]
+// CXX11-NEXT:    22: [B1.21] (CXXConstructExpr, const A)
+// CXX11-WARNINGS-NEXT:    23: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    23: A() (CXXConstructExpr, [B1.24], [B1.26], A)
+// CXX11-NEXT:    24: [B1.23] (BindTemporary)
+// CXX11-NEXT:    25: [B1.24] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    26: [B1.25]
+// CXX11-NEXT:    27: [B1.26] (CXXConstructExpr, const A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CXX11-NEXT:    29: {[B1.20], [B1.25]}
-// CXX11-NEXT:    30: [B1.29] (BindTemporary)
-// CXX11-NEXT:    31: [B1.30]
-// CXX11-NEXT:    32: {[B1.31]}
-// CXX11-WARNINGS-NEXT:    33: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    33: A() (CXXConstructExpr, [B1.34], [B1.36], A)
-// CXX11-NEXT:    34: [B1.33] (BindTemporary)
-// CXX11-NEXT:    35: [B1.34] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    36: [B1.35]
-// CXX11-NEXT:    37: [B1.36] (CXXConstructExpr, const A)
-// CXX11-WARNINGS-NEXT:    38: A() (CXXConstructExpr, A)
-// CXX11-ANALYZER-NEXT:    38: A() (CXXConstructExpr, [B1.39], [B1.41], A)
-// CXX11-NEXT:    39: [B1.38] (BindTemporary)
-// CXX11-NEXT:    40: [B1.39] (ImplicitCastExpr, NoOp, const A)
-// CXX11-NEXT:    41: [B1.40]
-// CXX11-NEXT:    42: [B1.41] (CXXConstructExpr, const A)
+// CXX11-NEXT:    28: {[B1.19], [B1.24]}
+// CXX11-NEXT:    29: [B1.28] (BindTemporary)
+// CXX11-NEXT:    30: [B1.29]
+// CXX11-NEXT:    31: {[B1.30]}
+// CXX11-WARNINGS-NEXT:    32: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    32: A() (CXXConstructExpr, [B1.33], [B1.35], A)
+// CXX11-NEXT:    33: [B1.32] (BindTemporary)
+// CXX11-NEXT:    34: [B1.33] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    35: [B1.34]
+// CXX11-NEXT:    36: [B1.35] (CXXConstructExpr, const A)
+// CXX11-WARNINGS-NEXT:    37: A() (CXXConstructExpr, A)
+// CXX11-ANALYZER-NEXT:    37: A() (CXXConstructExpr, [B1.38], [B1.40], A)
+// CXX11-NEXT:    38: [B1.37] (BindTemporary)
+// CXX11-NEXT:    39: [B1.38] (ImplicitCastExpr, NoOp, const A)
+// CXX11-NEXT:    40: [B1.39]
+// CXX11-NEXT:    41: [B1.40] (CXXConstructExpr, const A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CXX11-NEXT:    43: {[B1.34], [B1.39]}
-// CXX11-NEXT:    44: [B1.43] (BindTemporary)
-// CXX11-NEXT:    45: [B1.44]
-// CXX11-NEXT:    46: {[B1.45]}
+// CXX11-NEXT:    42: {[B1.33], [B1.38]}
+// CXX11-NEXT:    43: [B1.42] (BindTemporary)
+// CXX11-NEXT:    44: [B1.43]
+// CXX11-NEXT:    45: {[B1.44]}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CXX11-NEXT:    47: {{[{][{]}}[B1.31]}, {[B1.45]{{[}][}]}}
+// CXX11-NEXT:    46: {{[{][{]}}[B1.30]}, {[B1.44]{{[}][}]}}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CXX11-NEXT:    48: C cc[2] = {{[{][{][{]}}A(), A(){{[}][}]}}, {{[{][{]}}A(), A(){{[}][}][}]}};
-// CXX11-NEXT:    49: (FullExprCleanup collected 4 MTEs)
+// CXX11-NEXT:    47: C cc[2] = {{[{][{][{]}}A(), A(){{[}][}]}}, {{[{][{]}}A(), A(){{[}][}][}]}};
+// CXX11-NEXT:    48: ~A() (Temporary object destructor)
+// CXX11-NEXT:    49: ~A() (Temporary object destructor)
 // CXX11-NEXT:    50: ~A() (Temporary object destructor)
 // CXX11-NEXT:    51: ~A() (Temporary object destructor)
-// CXX11-NEXT:    52: ~A() (Temporary object destructor)
-// CXX11-NEXT:    53: ~A() (Temporary object destructor)
 // CXX11-NEXT:     Preds (1): B2
 // CXX11-NEXT:     Succs (1): B0
 // CXX11:        [B0 (EXIT)]
@@ -279,67 +277,65 @@ void test_aggregate_array_lifetime_extension() {
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
 // CHECK-NEXT:    17: {[B1.2], {[B1.7], [B1.12]}}
 // CHECK-NEXT:    18: D d = {A(), {A(), A()}};
-// CHECK-NEXT:    19: (FullExprCleanup collected 3 MTEs)
+// CHECK-NEXT:    19: ~A() (Temporary object destructor)
 // CHECK-NEXT:    20: ~A() (Temporary object destructor)
 // CHECK-NEXT:    21: ~A() (Temporary object destructor)
-// CHECK-NEXT:    22: ~A() (Temporary object destructor)
-// WARNINGS-NEXT:    23: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    23: A() (CXXConstructExpr, [B1.24], [B1.26], A)
-// CHECK-NEXT:    24: [B1.23] (BindTemporary)
-// CHECK-NEXT:    25: [B1.24] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    26: [B1.25]
-// CHECK-NEXT:    27: [B1.26] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    28: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    28: A() (CXXConstructExpr, [B1.29], [B1.31], A)
-// CHECK-NEXT:    29: [B1.28] (BindTemporary)
-// CHECK-NEXT:    30: [B1.29] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    31: [B1.30]
-// CHECK-NEXT:    32: [B1.31] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    33: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    33: A() (CXXConstructExpr, [B1.34], [B1.36], A)
-// CHECK-NEXT:    34: [B1.33] (BindTemporary)
-// CHECK-NEXT:    35: [B1.34] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    36: [B1.35]
-// CHECK-NEXT:    37: [B1.36] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    22: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    22: A() (CXXConstructExpr, [B1.23], [B1.25], A)
+// CHECK-NEXT:    23: [B1.22] (BindTemporary)
+// CHECK-NEXT:    24: [B1.23] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    25: [B1.24]
+// CHECK-NEXT:    26: [B1.25] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    27: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    27: A() (CXXConstructExpr, [B1.28], [B1.30], A)
+// CHECK-NEXT:    28: [B1.27] (BindTemporary)
+// CHECK-NEXT:    29: [B1.28] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    30: [B1.29]
+// CHECK-NEXT:    31: [B1.30] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    32: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    32: A() (CXXConstructExpr, [B1.33], [B1.35], A)
+// CHECK-NEXT:    33: [B1.32] (BindTemporary)
+// CHECK-NEXT:    34: [B1.33] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    35: [B1.34]
+// CHECK-NEXT:    36: [B1.35] (CXXConstructExpr, A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    38: {[B1.29], [B1.34]}
+// CHECK-NEXT:    37: {[B1.28], [B1.33]}
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    39: {[B1.24], {[B1.29], [B1.34]}}
-// WARNINGS-NEXT:    40: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    40: A() (CXXConstructExpr, [B1.41], [B1.43], A)
-// CHECK-NEXT:    41: [B1.40] (BindTemporary)
-// CHECK-NEXT:    42: [B1.41] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    43: [B1.42]
-// CHECK-NEXT:    44: [B1.43] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    45: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    45: A() (CXXConstructExpr, [B1.46], [B1.48], A)
-// CHECK-NEXT:    46: [B1.45] (BindTemporary)
-// CHECK-NEXT:    47: [B1.46] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    48: [B1.47]
-// CHECK-NEXT:    49: [B1.48] (CXXConstructExpr, A)
-// WARNINGS-NEXT:    50: A() (CXXConstructExpr, A)
-// ANALYZER-NEXT:    50: A() (CXXConstructExpr, [B1.51], [B1.53], A)
-// CHECK-NEXT:    51: [B1.50] (BindTemporary)
-// CHECK-NEXT:    52: [B1.51] (ImplicitCastExpr, NoOp, const A)
-// CHECK-NEXT:    53: [B1.52]
-// CHECK-NEXT:    54: [B1.53] (CXXConstructExpr, A)
+// CHECK-NEXT:    38: {[B1.23], {[B1.28], [B1.33]}}
+// WARNINGS-NEXT:    39: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    39: A() (CXXConstructExpr, [B1.40], [B1.42], A)
+// CHECK-NEXT:    40: [B1.39] (BindTemporary)
+// CHECK-NEXT:    41: [B1.40] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    42: [B1.41]
+// CHECK-NEXT:    43: [B1.42] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    44: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    44: A() (CXXConstructExpr, [B1.45], [B1.47], A)
+// CHECK-NEXT:    45: [B1.44] (BindTemporary)
+// CHECK-NEXT:    46: [B1.45] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    47: [B1.46]
+// CHECK-NEXT:    48: [B1.47] (CXXConstructExpr, A)
+// WARNINGS-NEXT:    49: A() (CXXConstructExpr, A)
+// ANALYZER-NEXT:    49: A() (CXXConstructExpr, [B1.50], [B1.52], A)
+// CHECK-NEXT:    50: [B1.49] (BindTemporary)
+// CHECK-NEXT:    51: [B1.50] (ImplicitCastExpr, NoOp, const A)
+// CHECK-NEXT:    52: [B1.51]
+// CHECK-NEXT:    53: [B1.52] (CXXConstructExpr, A)
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    55: {[B1.46], [B1.51]}
+// CHECK-NEXT:    54: {[B1.45], [B1.50]}
 // FIXME: Why does it look as if the initializer list consumes uncopied objects?
-// CHECK-NEXT:    56: {[B1.41], {[B1.46], [B1.51]}}
+// CHECK-NEXT:    55: {[B1.40], {[B1.45], [B1.50]}}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CHECK-NEXT:    57: {{[{][{]}}[B1.24], {[B1.29], [B1.34]{{[}][}]}}, {[B1.41], {[B1.46], [B1.51]{{[}][}][}]}}
+// CHECK-NEXT:    56: {{[{][{]}}[B1.23], {[B1.28], [B1.33]{{[}][}]}}, {[B1.40], {[B1.45], [B1.50]{{[}][}][}]}}
 // Double curly braces trigger regexps, escape as per FileCheck manual.
-// CHECK-NEXT:    58: D dd[2] = {{[{][{]}}A(), {A(), A(){{[}][}]}}, {A(), {A(), A(){{[}][}][}]}};
-// CHECK-NEXT:    59: (FullExprCleanup collected 6 MTEs)
+// CHECK-NEXT:    57: D dd[2] = {{[{][{]}}A(), {A(), A(){{[}][}]}}, {A(), {A(), A(){{[}][}][}]}};
+// CHECK-NEXT:    58: ~A() (Temporary object destructor)
+// CHECK-NEXT:    59: ~A() (Temporary object destructor)
 // CHECK-NEXT:    60: ~A() (Temporary object destructor)
 // CHECK-NEXT:    61: ~A() (Temporary object destructor)
 // CHECK-NEXT:    62: ~A() (Temporary object destructor)
 // CHECK-NEXT:    63: ~A() (Temporary object destructor)
-// CHECK-NEXT:    64: ~A() (Temporary object destructor)
-// CHECK-NEXT:    65: ~A() (Temporary object destructor)
-// CHECK-NEXT:    66: [B1.58].~D[2]() (Implicit destructor)
-// CHECK-NEXT:    67: [B1.18].~D() (Implicit destructor)
+// CHECK-NEXT:    64: [B1.57].~D[2]() (Implicit destructor)
+// CHECK-NEXT:    65: [B1.18].~D() (Implicit destructor)
 // CHECK-NEXT:     Preds (1): B2
 // CHECK-NEXT:     Succs (1): B0
 // CHECK:        [B0 (EXIT)]
diff --git a/clang/test/Analysis/cfg-rich-constructors.cpp b/clang/test/Analysis/cfg-rich-constructors.cpp
index c91eec9fda871..aea983f1f74da 100644
--- a/clang/test/Analysis/cfg-rich-constructors.cpp
+++ b/clang/test/Analysis/cfg-rich-constructors.cpp
@@ -253,22 +253,20 @@ class D: public C {
 // CHECK-NEXT:     4: [B1.3]
 // CHECK-NEXT:     5: [B1.4] (CXXConstructExpr, C([B1.4]) (Base initializer), C)
 // CHECK-NEXT:     6: C([B1.5]) (Base initializer)
-// CHECK-NEXT:     7: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:     8: CFGNewAllocator(C *)
-// CHECK-NEXT:     9: C::get
-// CHECK-NEXT:     10: [B1.9] (ImplicitCastExpr, FunctionToPointerDecay, C (*)(void))
-// CXX11-ELIDE-NEXT:    11: [B1.10]() (CXXRecordTypedCall, [B1.12], [B1.13])
-// CXX11-NOELIDE-NEXT:    11: [B1.10]() (CXXRecordTypedCall, [B1.12])
-// CXX11-NEXT:    12: [B1.11]
-// CXX11-NEXT:    13: [B1.12] (CXXConstructExpr, [B1.14], C)
-// CXX11-NEXT:    14: new C([B1.13])
-// CXX11-NEXT:    15: [B1.14] (CXXConstructExpr, c1([B1.14]) (Member initializer), C)
-// CXX11-NEXT:    16: c1([B1.15]) (Member initializer)
-// CXX11-NEXT:    17: (FullExprCleanup collected 1 MTEs)
-// CXX17-NEXT:    11: [B1.10]()
-// CXX17-NEXT:    12: new C([B1.11])
-// CXX17-NEXT:    13: [B1.12] (CXXConstructExpr, c1([B1.12]) (Member initializer), C)
-// CXX17-NEXT:    14: c1([B1.13]) (Member initializer)
+// CHECK-NEXT:     7: CFGNewAllocator(C *)
+// CHECK-NEXT:     8: C::get
+// CHECK-NEXT:     9: [B1.8] (ImplicitCastExpr, FunctionToPointerDecay, C (*)(void))
+// CXX11-ELIDE-NEXT:    10: [B1.9]() (CXXRecordTypedCall, [B1.11], [B1.12])
+// CXX11-NOELIDE-NEXT:    10: [B1.9]() (CXXRecordTypedCall, [B1.11])
+// CXX11-NEXT:    11: [B1.10]
+// CXX11-NEXT:    12: [B1.11] (CXXConstructExpr, [B1.13], C)
+// CXX11-NEXT:    13: new C([B1.12])
+// CXX11-NEXT:    14: [B1.13] (CXXConstructExpr, c1([B1.13]) (Member initializer), C)
+// CXX11-NEXT:    15: c1([B1.14]) (Member initializer)
+// CXX17-NEXT:    10: [B1.9]()
+// CXX17-NEXT:    11: new C([B1.10])
+// CXX17-NEXT:    12: [B1.11] (CXXConstructExpr, c1([B1.11]) (Member initializer), C)
+// CXX17-NEXT:    13: c1([B1.12]) (Member initializer)
   D(double): C(C::get()), c1(new C(C::get())) {}
 };
 
@@ -294,8 +292,7 @@ class F {
 // CXX11-NEXT:     6: [B1.5]
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, e([B1.6]) (Member initializer), E)
 // CXX11-NEXT:     8: e([B1.7]) (Member initializer)
-// CXX11-NEXT:     9: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     10: ~E() (Temporary object destructor)
+// CXX11-NEXT:     9: ~E() (Temporary object destructor)
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, e([B1.4]) (Member initializer), [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: e([B1.4]) (Member initializer)
@@ -348,9 +345,8 @@ C returnBracesWithMultipleItems() {
 // CXX11-ELIDE:    1: C() (CXXConstructExpr, [B1.2], [B1.3], C)
 // CXX11-NOELIDE:  1: C() (CXXConstructExpr, [B1.2], C)
 // CXX11-NEXT:     2: [B1.1]
-// CXX11-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.5], C)
-// CXX11-NEXT:     4: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     5: return [B1.3];
+// CXX11-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.4], C)
+// CXX11-NEXT:     4: return [B1.3];
 // CXX17:          1: C() (CXXConstructExpr, [B1.2], C)
 // CXX17-NEXT:     2: return [B1.1];
 C returnTemporary() {
@@ -365,9 +361,8 @@ C returnTemporary() {
 // CXX17-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.5], C)
 // CHECK-NEXT:     4: C([B1.3]) (CXXFunctionalCastExpr, ConstructorConversion, C)
 // CXX11-NEXT:     5: [B1.4]
-// CXX11-NEXT:     6: [B1.5] (CXXConstructExpr, [B1.8], C)
-// CXX11-NEXT:     7: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     8: return [B1.6];
+// CXX11-NEXT:     6: [B1.5] (CXXConstructExpr, [B1.7], C)
+// CXX11-NEXT:     7: return [B1.6];
 // CXX17-NEXT:     5: return [B1.4];
 
 C returnTemporaryWithArgument() {
@@ -380,9 +375,8 @@ C returnTemporaryWithArgument() {
 // CXX11-ELIDE-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.4], [B1.5])
 // CXX11-NOELIDE-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.4])
 // CXX11-NEXT:     4: [B1.3]
-// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.7], C)
-// CXX11-NEXT:     6: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     7: return [B1.5];
+// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.6], C)
+// CXX11-NEXT:     6: return [B1.5];
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.4])
 // CXX17-NEXT:     4: return [B1.3];
 C returnTemporaryConstructedByFunction() {
@@ -399,9 +393,8 @@ C returnTemporaryConstructedByFunction() {
 // CXX11-NOELIDE-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.7], C)
 // CXX11-NEXT:     6: C([B1.5]) (CXXFunctionalCastExpr, ConstructorConversion, C)
 // CXX11-NEXT:     7: [B1.6]
-// CXX11-NEXT:     8: [B1.7] (CXXConstructExpr, [B1.10], C)
-// CXX11-NEXT:     9: (FullExprCleanup collected 2 MTEs)
-// CXX11-NEXT:     10: return [B1.8];
+// CXX11-NEXT:     8: [B1.7] (CXXConstructExpr, [B1.9], C)
+// CXX11-NEXT:     9: return [B1.8];
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.5])
 // CXX17-NEXT:     4: C([B1.3]) (CXXFunctionalCastExpr, NoOp, C)
 // CXX17-NEXT:     5: return [B1.4];
@@ -425,10 +418,9 @@ class D {
 // CXX11-NEXT:     2: [B1.1] (BindTemporary)
 // CXX11-NEXT:     3: [B1.2] (ImplicitCastExpr, NoOp, const D)
 // CXX11-NEXT:     4: [B1.3]
-// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.8], D)
-// CXX11-NEXT:     6: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     7: ~D() (Temporary object destructor)
-// CXX11-NEXT:     8: return [B1.5];
+// CXX11-NEXT:     5: [B1.4] (CXXConstructExpr, [B1.7], D)
+// CXX11-NEXT:     6: ~D() (Temporary object destructor)
+// CXX11-NEXT:     7: return [B1.5];
 // CXX17:          1: D() (CXXConstructExpr, [B1.3], [B1.2], D)
 // CXX17-NEXT:     2: [B1.1] (BindTemporary)
 // CXX17-NEXT:     3: return [B1.2];
@@ -446,9 +438,8 @@ D returnTemporary() {
 // CXX11-NEXT:     6: [B1.5]
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, [B1.8], D)
 // CXX11-NEXT:     8: D d = returnTemporary();
-// CXX11-NEXT:     9: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     10: ~D() (Temporary object destructor)
-// CXX11-NEXT:    11: [B1.8].~D() (Implicit destructor)
+// CXX11-NEXT:     9: ~D() (Temporary object destructor)
+// CXX11-NEXT:    10: [B1.8].~D() (Implicit destructor)
 // CXX17-NEXT:     3: [B1.2]() (CXXRecordTypedCall, [B1.5], [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: D d = returnTemporary();
@@ -475,7 +466,6 @@ void simpleTemporary() {
 // CHECK-NEXT:     4: [B2.3].operator bool
 // CHECK-NEXT:     5: [B2.3]
 // CHECK-NEXT:     6: [B2.5] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK-NEXT:     7: (FullExprCleanup collected 1 MTEs)
 // CHECK-NEXT:     T: if [B2.6]
 void temporaryInCondition() {
   if (C());
@@ -487,13 +477,12 @@ void temporaryInCondition() {
 // CXX11-NEXT:     2: [B2.1]
 // CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.4], C)
 // CXX11-NEXT:     4: C c = C();
-// CXX11-NEXT:     5: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     6: c
-// CXX11-NEXT:     7: [B2.6] (ImplicitCastExpr, NoOp, const class C)
-// CXX11-NEXT:     8: [B2.7].operator bool
-// CXX11-NEXT:     9: [B2.7]
-// CXX11-NEXT:     10: [B2.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11-NEXT:     T: if [B2.10]
+// CXX11-NEXT:     5: c
+// CXX11-NEXT:     6: [B2.5] (ImplicitCastExpr, NoOp, const class C)
+// CXX11-NEXT:     7: [B2.6].operator bool
+// CXX11-NEXT:     8: [B2.6]
+// CXX11-NEXT:     9: [B2.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11-NEXT:     T: if [B2.9]
 // CXX17:          1: C() (CXXConstructExpr, [B2.2], C)
 // CXX17-NEXT:     2: C c = C();
 // CXX17-NEXT:     3: c
@@ -512,15 +501,14 @@ void temporaryInConditionVariable() {
 // CXX11-ELIDE-NEXT:     1: C() (CXXConstructExpr, [B2.2], [B2.3], C)
 // CXX11-NOELIDE-NEXT:     1: C() (CXXConstructExpr, [B2.2], C)
 // CXX11-NEXT:     2: [B2.1]
-// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.5], C)
-// CXX11-NEXT:     4: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     5: C c2 = C();
-// CXX11-NEXT:     6: c2
-// CXX11-NEXT:     7: [B2.6] (ImplicitCastExpr, NoOp, const class C)
-// CXX11-NEXT:     8: [B2.7].operator bool
-// CXX11-NEXT:     9: [B2.7]
-// CXX11-NEXT:     10: [B2.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11-NEXT:     T: for (...; [B2.10]; )
+// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.4], C)
+// CXX11-NEXT:     4: C c2 = C();
+// CXX11-NEXT:     5: c2
+// CXX11-NEXT:     6: [B2.5] (ImplicitCastExpr, NoOp, const class C)
+// CXX11-NEXT:     7: [B2.6].operator bool
+// CXX11-NEXT:     8: [B2.6]
+// CXX11-NEXT:     9: [B2.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11-NEXT:     T: for (...; [B2.9]; )
 // CXX17-NEXT:     1: C() (CXXConstructExpr, [B2.2], C)
 // CXX17-NEXT:     2: C c2 = C();
 // CXX17-NEXT:     3: c2
@@ -546,15 +534,14 @@ void temporaryInForLoopConditionVariable() {
 // CXX11-ELIDE:          1: C() (CXXConstructExpr, [B2.2], [B2.3], C)
 // CXX11-NOELIDE:          1: C() (CXXConstructExpr, [B2.2], C)
 // CXX11-NEXT:     2: [B2.1]
-// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.5], C)
-// CXX11-NEXT:     4: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:     5: C c = C();
-// CXX11-NEXT:     6: c
-// CXX11-NEXT:     7: [B2.6] (ImplicitCastExpr, NoOp, const class C)
-// CXX11-NEXT:     8: [B2.7].operator bool
-// CXX11-NEXT:     9: [B2.7
-// CXX11-NEXT:     10: [B2.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11-NEXT:     T: while [B2.10]
+// CXX11-NEXT:     3: [B2.2] (CXXConstructExpr, [B2.4], C)
+// CXX11-NEXT:     4: C c = C();
+// CXX11-NEXT:     5: c
+// CXX11-NEXT:     6: [B2.5] (ImplicitCastExpr, NoOp, const class C)
+// CXX11-NEXT:     7: [B2.6].operator bool
+// CXX11-NEXT:     8: [B2.6]
+// CXX11-NEXT:     9: [B2.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11-NEXT:     T: while [B2.9]
 // CXX17:          1: C() (CXXConstructExpr, [B2.2], C)
 // CXX17-NEXT:     2: C c = C();
 // CXX17-NEXT:     3: c
@@ -598,8 +585,7 @@ void simpleTemporary() {
 // CHECK-NEXT:     5: [B2.4].operator bool
 // CHECK-NEXT:     6: [B2.4]
 // CHECK-NEXT:     7: [B2.6] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK-NEXT:     8: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:     9: ~D() (Temporary object destructor)
+// CHECK-NEXT:     8: ~D() (Temporary object destructor)
 // CHECK-NEXT:     T: if [B2.7]
 void temporaryInCondition() {
   if (D());
@@ -639,7 +625,6 @@ void referenceVariableWithInitializer() {
 // CXX11-NEXT:     2: [B4.1] (ImplicitCastExpr, NoOp, const D)
 // CXX11-NEXT:     3: [B4.2]
 // CXX11-NEXT:     4: const D &d = coin ? D::get() : D(0);
-// CXX11-NEXT:     5: (FullExprCleanup collected 2 MTEs)
 // CXX11-NEXT:     T: (Temp Dtor) [B6.3]
 // CXX11:        [B5]
 // CXX11-NEXT:     1: D::get
@@ -752,16 +737,14 @@ class B {
 // CXX11-NEXT:     8: [B1.7]
 // CXX11-NEXT:     9: [B1.8] (CXXConstructExpr, [B1.10], B)
 // CXX11-NEXT:    10: B b = A();
-// CXX11-NEXT:    11: (FullExprCleanup collected 2 MTEs)
-// CXX11-NEXT:    12: ~B() (Temporary object destructor)
-// CXX11-NEXT:    13: [B1.10].~B() (Implicit destructor)
+// CXX11-NEXT:    11: ~B() (Temporary object destructor)
+// CXX11-NEXT:    12: [B1.10].~B() (Implicit destructor)
 // CXX17-NEXT:     2: [B1.1] (ImplicitCastExpr, NoOp, const A)
 // CXX17-NEXT:     3: [B1.2]
 // CXX17-NEXT:     4: [B1.3] (CXXConstructExpr, [B1.6], B)
 // CXX17-NEXT:     5: [B1.4] (ImplicitCastExpr, ConstructorConversion, B)
 // CXX17-NEXT:     6: B b = A();
-// CXX17-NEXT:     7: (FullExprCleanup collected 1 MTEs)
-// CXX17-NEXT:     8: [B1.6].~B() (Implicit destructor)
+// CXX17-NEXT:     7: [B1.6].~B() (Implicit destructor)
 void implicitConstructionConversionFromTemporary() {
   B b = A();
 }
@@ -780,14 +763,12 @@ void implicitConstructionConversionFromTemporary() {
 // CXX11-NEXT:    10: [B1.9]
 // CXX11-NEXT:    11: [B1.10] (CXXConstructExpr, [B1.12], B)
 // CXX11-NEXT:    12: B b = get();
-// CXX11-NEXT:    13: (FullExprCleanup collected 2 MTEs)
-// CXX11-NEXT:    14: ~B() (Temporary object destructor)
-// CXX11-NEXT:    15: [B1.12].~B() (Implicit destructor)
+// CXX11-NEXT:    13: ~B() (Temporary object destructor)
+// CXX11-NEXT:    14: [B1.12].~B() (Implicit destructor)
 // CXX17-NEXT:     6: [B1.5] (CXXConstructExpr, [B1.8], B)
 // CXX17-NEXT:     7: [B1.6] (ImplicitCastExpr, ConstructorConversion, B)
 // CXX17-NEXT:     8: B b = get();
-// CXX17-NEXT:     9: (FullExprCleanup collected 1 MTEs)
-// CXX17-NEXT:    10: [B1.8].~B() (Implicit destructor)
+// CXX17-NEXT:     9: [B1.8].~B() (Implicit destructor)
 void implicitConstructionConversionFromFunctionValue() {
   B b = get();
 }
@@ -801,8 +782,7 @@ void implicitConstructionConversionFromFunctionValue() {
 // CHECK-NEXT:     6: [B1.5] (ImplicitCastExpr, NoOp, const B)
 // CHECK-NEXT:     7: [B1.6]
 // CHECK-NEXT:     8: const B &b = A();
-// CHECK-NEXT:     9: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:     10: [B1.8].~B() (Implicit destructor)
+// CHECK-NEXT:     9: [B1.8].~B() (Implicit destructor)
 void implicitConstructionConversionFromTemporaryWithLifetimeExtension() {
   const B &b = A();
 }
@@ -818,8 +798,7 @@ void implicitConstructionConversionFromTemporaryWithLifetimeExtension() {
 // CHECK-NEXT:     8: [B1.7] (ImplicitCastExpr, NoOp, const B)
 // CHECK-NEXT:     9: [B1.8]
 // CHECK-NEXT:    10: const B &b = get();
-// CHECK-NEXT:    11: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:    12: [B1.10].~B() (Implicit destructor)
+// CHECK-NEXT:    11: [B1.10].~B() (Implicit destructor)
 void implicitConstructionConversionFromFunctionValueWithLifetimeExtension() {
   const B &b = get(); // no-crash
 }
@@ -875,9 +854,8 @@ void passArgument() {
 // CXX11-NEXT:    10: [B1.9] (CXXConstructExpr, [B1.11], [B1.12]+1, D)
 // CXX11-NEXT:    11: [B1.10] (BindTemporary)
 // CXX11-NEXT:    12: [B1.2]([B1.5], [B1.11])
-// CXX11-NEXT:    13: (FullExprCleanup collected 2 MTEs)
+// CXX11-NEXT:    13: ~D() (Temporary object destructor)
 // CXX11-NEXT:    14: ~D() (Temporary object destructor)
-// CXX11-NEXT:    15: ~D() (Temporary object destructor)
 // CXX17-NEXT:     3: C() (CXXConstructExpr, [B1.6]+0, C)
 // CXX17-NEXT:     4: D() (CXXConstructExpr, [B1.5], [B1.6]+1, D)
 // CXX17-NEXT:     5: [B1.4] (BindTemporary)
@@ -909,9 +887,8 @@ void passArgumentByReference() {
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, [B1.8], [B1.9]+0, D)
 // CXX11-NEXT:     8: [B1.7] (BindTemporary)
 // CXX11-NEXT:     9: [B1.2]([B1.8])
-// CXX11-NEXT:    10: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:    10: ~D() (Temporary object destructor)
 // CXX11-NEXT:    11: ~D() (Temporary object destructor)
-// CXX11-NEXT:    12: ~D() (Temporary object destructor)
 // CXX17-NEXT:     3: D() (CXXConstructExpr, [B1.4], [B1.5]+0, D)
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // CXX17-NEXT:     5: [B1.2]([B1.4])
@@ -928,8 +905,7 @@ void passArgumentWithDestructor() {
 // CHECK-NEXT:     5: [B1.4] (ImplicitCastExpr, NoOp, const D)
 // CHECK-NEXT:     6: [B1.5]
 // CHECK-NEXT:     7: [B1.2]([B1.6])
-// CHECK-NEXT:     8: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:     9: ~D() (Temporary object destructor)
+// CHECK-NEXT:     8: ~D() (Temporary object destructor)
 void passArgumentWithDestructorByReference() {
   useDByReference(D());
 }
@@ -948,9 +924,8 @@ void passArgumentWithDestructorByReference() {
 // CXX11-NEXT:     9: [B1.8]
 // CXX11-NEXT:    10: [B1.9] (CXXConstructExpr, [B1.11], E)
 // CXX11-NEXT:    11: E e = E(D());
-// CXX11-NEXT:    12: (FullExprCleanup collected 2 MTEs)
+// CXX11-NEXT:    12: ~D() (Temporary object destructor)
 // CXX11-NEXT:    13: ~D() (Temporary object destructor)
-// CXX11-NEXT:    14: ~D() (Temporary object destructor)
 // CXX17:          1: D() (CXXConstructExpr, [B1.2], [B1.3]+0, D)
 // CXX17-NEXT:     2: [B1.1] (BindTemporary)
 // CXX17-NEXT:     3: [B1.2] (CXXConstructExpr, [B1.5], E)
@@ -978,11 +953,10 @@ void passArgumentIntoAnotherConstructor() {
 // CXX11-NEXT:    11: [B1.10] (CXXConstructExpr, [B1.12], [B1.13]+1, D)
 // CXX11-NEXT:    12: [B1.11] (BindTemporary)
 // CXX11-NEXT:    13: E([B1.6], [B1.12]) (CXXConstructExpr, E)
-// CXX11-NEXT:    14: (FullExprCleanup collected 2 MTEs)
+// CXX11-NEXT:    14: ~D() (Temporary object destructor)
 // CXX11-NEXT:    15: ~D() (Temporary object destructor)
 // CXX11-NEXT:    16: ~D() (Temporary object destructor)
 // CXX11-NEXT:    17: ~D() (Temporary object destructor)
-// CXX11-NEXT:    18: ~D() (Temporary object destructor)
 // CXX17:          1: D() (CXXConstructExpr, [B1.2], [B1.5]+0, D)
 // CXX17-NEXT:     2: [B1.1] (BindTemporary)
 // CXX17-NEXT:     3: D() (CXXConstructExpr, [B1.4], [B1.5]+1, D)
@@ -1060,7 +1034,6 @@ int variadic(...);
 
 // This code is quite exotic, so let's not test the CFG for it,
 // but only make sure we don't crash.
-// CHECK-LABEL: void testCrashOnVariadicArgument()
 void testCrashOnVariadicArgument() {
   C c(variadic(0 ? c : 0)); // no-crash
 }
diff --git a/clang/test/Analysis/cfg-rich-constructors.mm b/clang/test/Analysis/cfg-rich-constructors.mm
index 1b73c7ce55f76..f048f061e9fba 100644
--- a/clang/test/Analysis/cfg-rich-constructors.mm
+++ b/clang/test/Analysis/cfg-rich-constructors.mm
@@ -30,9 +30,8 @@ -(D) bar;
 // CXX11-NEXT:     8: [B1.7] (BindTemporary)
 // Double brackets trigger FileCheck variables, escape.
 // CXX11-NEXT:     9: {{\[}}[B1.2] foo:[B1.8]]
-// CXX11-NEXT:    10: (FullExprCleanup collected 1 MTEs)
+// CXX11-NEXT:    10: ~D() (Temporary object destructor)
 // CXX11-NEXT:    11: ~D() (Temporary object destructor)
-// CXX11-NEXT:    12: ~D() (Temporary object destructor)
 // CXX17-NEXT:     3: D() (CXXConstructExpr, [B1.4], [B1.5]+0, D)
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
 // Double brackets trigger FileCheck variables, escape.
@@ -54,9 +53,8 @@ void passArgumentIntoMessage(E *e) {
 // CXX11-NEXT:     6: [B1.5]
 // CXX11-NEXT:     7: [B1.6] (CXXConstructExpr, [B1.8], D)
 // CXX11-NEXT:     8: D d = [e bar];
-// CXX11-NEXT:     9: (FullExprCleanup collected 1 MTEs)
-// CXX11-NEXT:    10: ~D() (Temporary object destructor)
-// CXX11-NEXT:    11: [B1.8].~D() (Implicit destructor)
+// CXX11-NEXT:     9: ~D() (Temporary object destructor)
+// CXX11-NEXT:    10: [B1.8].~D() (Implicit destructor)
 // Double brackets trigger FileCheck variables, escape.
 // CXX17-NEXT:     3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.5], [B1.4])
 // CXX17-NEXT:     4: [B1.3] (BindTemporary)
diff --git a/clang/test/Analysis/cfg.cpp b/clang/test/Analysis/cfg.cpp
index 9a0a60110876b..2a88b73d27756 100644
--- a/clang/test/Analysis/cfg.cpp
+++ b/clang/test/Analysis/cfg.cpp
@@ -390,7 +390,6 @@ void test_lifetime_extended_temporaries() {
     3;
   }
   // CHECK: LifetimeExtend(4)
-  // CHECK-NEXT: (FullExprCleanup collected 2 MTEs)
   // CHECK-NEXT: ~LifetimeExtend()
   // CHECK-NEXT: ~LifetimeExtend()
   // CHECK-NEXT: : 4
diff --git a/clang/test/Analysis/missing-bind-temporary.cpp b/clang/test/Analysis/missing-bind-temporary.cpp
index 78bcb570a3ce5..3d1af469dc01c 100644
--- a/clang/test/Analysis/missing-bind-temporary.cpp
+++ b/clang/test/Analysis/missing-bind-temporary.cpp
@@ -28,9 +28,8 @@ class B {
 // CHECK-NEXT:    7: [B1.6] (BindTemporary)
 // CHECK-NEXT:    8: [B1.7]
 // CHECK-NEXT:    9: [B1.5] = [B1.8] (OperatorCall)
-// CHECK-NEXT:   10: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:   11: ~B() (Temporary object destructor)
-// CHECK-NEXT:   12: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:   10: ~B() (Temporary object destructor)
+// CHECK-NEXT:   11: [B1.2].~B() (Implicit destructor)
 void foo(int) {
   B i;
   i = {};
@@ -66,9 +65,8 @@ class B {
 // CHECK-NEXT:    7: [B1.6] (BindTemporary)
 // CHECK-NEXT:    8: [B1.7]
 // CHECK-NEXT:    9: [B1.5] = [B1.8] (OperatorCall)
-// CHECK-NEXT:    10: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:    11: ~B() (Temporary object destructor)
-// CHECK-NEXT:    12: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:    10: ~B() (Temporary object destructor)
+// CHECK-NEXT:    11: [B1.2].~B() (Implicit destructor)
 template <typename T> void foo(T) {
   B i;
   i = {};
@@ -109,9 +107,8 @@ class B {
 // CHECK-NEXT:    8: [B1.7] (BindTemporary)
 // CHECK-NEXT:    9: [B1.8]
 // CHECK-NEXT:   10: [B1.5] = [B1.9] (OperatorCall)
-// CHECK-NEXT:   11: (FullExprCleanup collected 1 MTEs)
-// CHECK-NEXT:   12: ~B() (Temporary object destructor)
-// CHECK-NEXT:   13: [B1.2].~B() (Implicit destructor)
+// CHECK-NEXT:   11: ~B() (Temporary object destructor)
+// CHECK-NEXT:   12: [B1.2].~B() (Implicit destructor)
 template <typename T> void foo(T) {
   B i;
   i = {};
diff --git a/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp b/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp
index 22807741fdae1..b496d261e92f4 100644
--- a/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp
+++ b/clang/test/Analysis/temp-obj-dtors-cfg-output.cpp
@@ -240,10 +240,9 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B1.2] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     4: [B1.3]
 // WARNINGS:     5: [B1.4] (CXXConstructExpr, A)
-// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.8], A)
-// CHECK:     6: (FullExprCleanup collected 1 MTEs)
-// CHECK:     7: ~A() (Temporary object destructor)
-// CHECK:     8: return [B1.5];
+// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.7], A)
+// CHECK:     6: ~A() (Temporary object destructor)
+// CHECK:     7: return [B1.5];
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -301,10 +300,9 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B1.2] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     4: [B1.3]
 // WARNINGS:     5: [B1.4] (CXXConstructExpr, A)
-// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.8], A)
-// CHECK:     6: (FullExprCleanup collected 1 MTEs)
-// CHECK:     7: ~A() (Temporary object destructor)
-// CHECK:     8: return [B1.5];
+// ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.7], A)
+// CHECK:     6: ~A() (Temporary object destructor)
+// CHECK:     7: return [B1.5];
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -330,33 +328,31 @@ const C &bar3(bool coin) {
 // CHECK:    14: int([B1.13]) (CXXFunctionalCastExpr, NoOp, int)
 // CHECK:    15: [B1.7] + [B1.14]
 // CHECK:    16: int a = int(A()) + int(B());
-// CHECK:    17: (FullExprCleanup collected 2 MTEs)
-// CHECK:    18: ~B() (Temporary object destructor)
-// CHECK:    19: ~A() (Temporary object destructor)
-// CHECK:    20: foo
-// CHECK:    21: [B1.20] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(int))
-// WARNINGS:    22: A() (CXXConstructExpr, A)
-// ANALYZER:    22: A() (CXXConstructExpr, [B1.23], [B1.24], A)
-// CHECK:    23: [B1.22] (BindTemporary)
-// CHECK:    24: [B1.23]
-// CHECK:    25: [B1.24].operator int
-// CHECK:    26: [B1.24]
-// CHECK:    27: [B1.26] (ImplicitCastExpr, UserDefinedConversion, int)
-// CHECK:    28: int([B1.27]) (CXXFunctionalCastExpr, NoOp, int)
-// WARNINGS:    29: B() (CXXConstructExpr, B)
-// ANALYZER:    29: B() (CXXConstructExpr, [B1.30], [B1.31], B)
-// CHECK:    30: [B1.29] (BindTemporary)
-// CHECK:    31: [B1.30]
-// CHECK:    32: [B1.31].operator int
-// CHECK:    33: [B1.31]
-// CHECK:    34: [B1.33] (ImplicitCastExpr, UserDefinedConversion, int)
-// CHECK:    35: int([B1.34]) (CXXFunctionalCastExpr, NoOp, int)
-// CHECK:    36: [B1.28] + [B1.35]
-// CHECK:    37: [B1.21]([B1.36])
-// CHECK:    38: (FullExprCleanup collected 2 MTEs)
-// CHECK:    39: ~B() (Temporary object destructor)
-// CHECK:    40: ~A() (Temporary object destructor)
-// CHECK:    41: int b;
+// CHECK:    17: ~B() (Temporary object destructor)
+// CHECK:    18: ~A() (Temporary object destructor)
+// CHECK:    19: foo
+// CHECK:    20: [B1.19] (ImplicitCastExpr, FunctionToPointerDecay, void (*)(int))
+// WARNINGS:    21: A() (CXXConstructExpr, A)
+// ANALYZER:    21: A() (CXXConstructExpr, [B1.22], [B1.23], A)
+// CHECK:    22: [B1.21] (BindTemporary)
+// CHECK:    23: [B1.22]
+// CHECK:    24: [B1.23].operator int
+// CHECK:    25: [B1.23]
+// CHECK:    26: [B1.25] (ImplicitCastExpr, UserDefinedConversion, int)
+// CHECK:    27: int([B1.26]) (CXXFunctionalCastExpr, NoOp, int)
+// WARNINGS:    28: B() (CXXConstructExpr, B)
+// ANALYZER:    28: B() (CXXConstructExpr, [B1.29], [B1.30], B)
+// CHECK:    29: [B1.28] (BindTemporary)
+// CHECK:    30: [B1.29]
+// CHECK:    31: [B1.30].operator int
+// CHECK:    32: [B1.30]
+// CHECK:    33: [B1.32] (ImplicitCastExpr, UserDefinedConversion, int)
+// CHECK:    34: int([B1.33]) (CXXFunctionalCastExpr, NoOp, int)
+// CHECK:    35: [B1.27] + [B1.34]
+// CHECK:    36: [B1.20]([B1.35])
+// CHECK:    37: ~B() (Temporary object destructor)
+// CHECK:    38: ~A() (Temporary object destructor)
+// CHECK:    39: int b;
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -375,7 +371,6 @@ const C &bar3(bool coin) {
 // CHECK:   [B3]
 // CHECK:     1: [B5.9] && [B4.6]
 // CHECK:     2: [B5.3]([B3.1])
-// CHECK:     3: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B4.2]
 // CHECK:     Preds (2): B4 B5
 // CHECK:     Succs (2): B2 B1
@@ -410,7 +405,6 @@ const C &bar3(bool coin) {
 // CHECK:   [B7]
 // CHECK:     1: [B9.6] && [B8.6]
 // CHECK:     2: bool a = A() && B();
-// CHECK:     3: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B8.2]
 // CHECK:     Preds (2): B8 B9
 // CHECK:     Succs (2): B6 B5
@@ -485,7 +479,6 @@ const C &bar3(bool coin) {
 // CHECK:   [B7]
 // CHECK:     1: [B9.6] || [B8.6]
 // CHECK:     2: bool a = A() || B();
-// CHECK:     3: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B8.2]
 // CHECK:     Preds (2): B8 B9
 // CHECK:     Succs (2): B6 B5
@@ -542,8 +535,7 @@ const C &bar3(bool coin) {
 // CHECK:     5: [B4.4].operator bool
 // CHECK:     6: [B4.4]
 // CHECK:     7: [B4.6] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK:     8: (FullExprCleanup collected 1 MTEs)
-// CHECK:     9: ~B() (Temporary object destructor)
+// CHECK:     8: ~B() (Temporary object destructor)
 // CHECK:     T: if [B4.7]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -566,7 +558,6 @@ const C &bar3(bool coin) {
 // WARNINGS:     4: [B7.3] (CXXConstructExpr, A)
 // ANALYZER:     4: [B7.3] (CXXConstructExpr, [B7.5], A)
 // CHECK:     5: A a = B() ? A() : A(B());
-// CHECK:     6: (FullExprCleanup collected 6 MTEs)
 // CHECK:     T: (Temp Dtor) [B9.2]
 // CHECK:     Preds (2): B8 B9
 // CHECK:     Succs (2): B6 B5
@@ -680,8 +671,7 @@ const C &bar3(bool coin) {
 // CHECK:     4: [B3.3].operator bool
 // CHECK:     5: [B3.3]
 // CHECK:     6: [B3.5] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK:     7: (FullExprCleanup collected 1 MTEs)
-// CHECK:     8: ~C() (Temporary object destructor)
+// CHECK:     7: ~C() (Temporary object destructor)
 // CHECK:     T: if [B3.6]
 // CHECK:     Preds (1): B4
 // CHECK:     Succs (2): B2 B1
@@ -713,13 +703,12 @@ const C &bar3(bool coin) {
 // WARNINGS:     5: [B4.4] (CXXConstructExpr, C)
 // ANALYZER:     5: [B4.4] (CXXConstructExpr, [B4.6], C)
 // CHECK:     6: C c = C();
-// CHECK:     7: (FullExprCleanup collected 1 MTEs)
-// CHECK:     8: ~C() (Temporary object destructor)
-// CHECK:     9: c
-// CHECK:    10: [B4.9].operator bool
-// CHECK:    11: [B4.9]
-// CHECK:    12: [B4.11] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK:     T: if [B4.12]
+// CHECK:     7: ~C() (Temporary object destructor)
+// CHECK:     8: c
+// CHECK:     9: [B4.8].operator bool
+// CHECK:    10: [B4.8]
+// CHECK:    11: [B4.10] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CHECK:     T: if [B4.11]
 // CHECK:     Preds (1): B5
 // CHECK:     Succs (2): B3 B2
 // CHECK:   [B0 (EXIT)]
@@ -743,7 +732,6 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B3.2].operator bool
 // CHECK:     4: [B3.2]
 // CHECK:     5: [B3.4] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CHECK:     6: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: if [B3.5]
 // CHECK:     Preds (1): B4
 // CHECK:     Succs (2): B2 B1
@@ -769,24 +757,22 @@ const C &bar3(bool coin) {
 // CXX98-WARNINGS:     4: [B3.3] (CXXConstructExpr, D)
 // CXX98-ANALYZER:     4: [B3.3] (CXXConstructExpr, [B3.5], D)
 // CXX98:     5: D d = D();
-// CXX98:     6: (FullExprCleanup collected 1 MTEs)
-// CXX98:     7: d
-// CXX98:     8: [B3.7].operator bool
-// CXX98:     9: [B3.7]
-// CXX98:    10: [B3.9] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX98:     T: if [B3.10]
+// CXX98:     6: d
+// CXX98:     7: [B3.6].operator bool
+// CXX98:     8: [B3.6]
+// CXX98:     9: [B3.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX98:     T: if [B3.9]
 // CXX11-WARNINGS:     1: D() (CXXConstructExpr, D)
 // CXX11-ANALYZER:     1: D() (CXXConstructExpr, [B3.2], [B3.3], D)
 // CXX11:     2: [B3.1]
 // CXX11-WARNINGS:     3: [B3.2] (CXXConstructExpr, D)
 // CXX11-ANALYZER:     3: [B3.2] (CXXConstructExpr, [B3.4], D)
 // CXX11:     4: D d = D();
-// CXX11:     5: (FullExprCleanup collected 1 MTEs)
-// CXX11:     6: d
-// CXX11:     7: [B3.6].operator bool
-// CXX11:     8: [B3.6]
-// CXX11:     9: [B3.8] (ImplicitCastExpr, UserDefinedConversion, _Bool)
-// CXX11:     T: if [B3.9]
+// CXX11:     5: d
+// CXX11:     6: [B3.5].operator bool
+// CXX11:     7: [B3.5]
+// CXX11:     8: [B3.7] (ImplicitCastExpr, UserDefinedConversion, _Bool)
+// CXX11:     T: if [B3.8]
 // CHECK:     Preds (1): B4
 // CHECK:     Succs (2): B2 B1
 // CHECK:   [B0 (EXIT)]
@@ -816,7 +802,6 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B4.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B4.2]
 // CHECK:     4: [B7.3]([B4.3])
-// CHECK:     5: (FullExprCleanup collected 6 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.2]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -882,7 +867,6 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B10.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B10.2]
 // CHECK:     4: const A &a = B() ? A() : A(B());
-// CHECK:     5: (FullExprCleanup collected 5 MTEs)
 // CHECK:     T: (Temp Dtor) [B12.2]
 // CHECK:     Preds (2): B11 B12
 // CHECK:     Succs (2): B9 B8
@@ -956,8 +940,6 @@ const C &bar3(bool coin) {
 // WARNINGS:     4: [B4.3] (CXXConstructExpr, A)
 // ANALYZER:     4: [B4.3] (CXXConstructExpr, [B4.5], A)
 // CHECK:     5: A a = A() ?: A();
-// CXX98:     6: (FullExprCleanup collected 4 MTEs)
-// CXX11:     6: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.2]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -1020,8 +1002,6 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B4.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B4.2]
 // CHECK:     4: [B7.2]([B4.3])
-// CXX98:     5: (FullExprCleanup collected 4 MTEs)
-// CXX11:     5: (FullExprCleanup collected 2 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.2]
 // CHECK:     Preds (2): B5 B6
 // CHECK:     Succs (2): B3 B2
@@ -1072,8 +1052,6 @@ const C &bar3(bool coin) {
 // CHECK:     2: [B9.1] (ImplicitCastExpr, NoOp, const A)
 // CHECK:     3: [B9.2]
 // CHECK:     4: const A &a = A() ?: A();
-// CXX98:     5: (FullExprCleanup collected 3 MTEs)
-// CXX11:     5: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B11.2]
 // CHECK:     Preds (2): B10 B11
 // CHECK:     Succs (2): B8 B7
@@ -1125,10 +1103,9 @@ const C &bar3(bool coin) {
 // WARNINGS:     5: [B1.4] (CXXConstructExpr, A)
 // ANALYZER:     5: [B1.4] (CXXConstructExpr, [B1.6], A)
 // CHECK:     6: A a = A();
-// CHECK:     7: (FullExprCleanup collected 1 MTEs)
-// CHECK:     8: ~A() (Temporary object destructor)
-// CHECK:     9: int b;
-// CHECK:    10: [B1.6].~A() (Implicit destructor)
+// CHECK:     7: ~A() (Temporary object destructor)
+// CHECK:     8: int b;
+// CHECK:     9: [B1.6].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1150,10 +1127,9 @@ const C &bar3(bool coin) {
 // CHECK:    10: [B1.9] (ImplicitCastExpr, NoOp, const A)
 // CHECK:    11: [B1.10]
 // CHECK:    12: [B1.7]([B1.11])
-// CHECK:    13: (FullExprCleanup collected 1 MTEs)
-// CHECK:    14: ~A() (Temporary object destructor)
-// CHECK:    15: int b;
-// CHECK:    16: [B1.5].~A() (Implicit destructor)
+// CHECK:    13: ~A() (Temporary object destructor)
+// CHECK:    14: int b;
+// CHECK:    15: [B1.5].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1171,10 +1147,9 @@ const C &bar3(bool coin) {
 // WARNINGS:     7: [B1.6] (CXXConstructExpr, A)
 // ANALYZER:     7: [B1.6] (CXXConstructExpr, [B1.8], A)
 // CHECK:     8: A a = A::make();
-// CHECK:     9: (FullExprCleanup collected 1 MTEs)
-// CHECK:    10: ~A() (Temporary object destructor)
-// CHECK:    11: int b;
-// CHECK:    12: [B1.8].~A() (Implicit destructor)
+// CHECK:     9: ~A() (Temporary object destructor)
+// CHECK:    10: int b;
+// CHECK:    11: [B1.8].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1200,10 +1175,9 @@ const C &bar3(bool coin) {
 // CHECK:    14: [B1.13] (ImplicitCastExpr, NoOp, const A)
 // CHECK:    15: [B1.14]
 // CHECK:    16: [B1.9]([B1.15])
-// CHECK:    17: (FullExprCleanup collected 1 MTEs)
-// CHECK:    18: ~A() (Temporary object destructor)
-// CHECK:    19: int b;
-// CHECK:    20: [B1.7].~A() (Implicit destructor)
+// CHECK:    17: ~A() (Temporary object destructor)
+// CHECK:    18: int b;
+// CHECK:    19: [B1.7].~A() (Implicit destructor)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1221,9 +1195,8 @@ const C &bar3(bool coin) {
 // CHECK:     7: [B1.6] (ImplicitCastExpr, UserDefinedConversion, int)
 // CHECK:     8: a
 // CHECK:     9: [B1.8] = [B1.7]
-// CHECK:    10: (FullExprCleanup collected 1 MTEs)
-// CHECK:    11: ~A() (Temporary object destructor)
-// CHECK:    12: int b;
+// CHECK:    10: ~A() (Temporary object destructor)
+// CHECK:    11: int b;
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1249,10 +1222,9 @@ const C &bar3(bool coin) {
 // CHECK:    14: int([B1.13]) (CXXFunctionalCastExpr, NoOp, int)
 // CHECK:    15: [B1.7] + [B1.14]
 // CHECK:    16: a([B1.15]) (Member initializer)
-// CHECK:    17: (FullExprCleanup collected 2 MTEs)
-// CHECK:    18: ~B() (Temporary object destructor)
-// CHECK:    19: ~A() (Temporary object destructor)
-// CHECK:    20: b(/*implicit*/(int)0) (Member initializer)
+// CHECK:    17: ~B() (Temporary object destructor)
+// CHECK:    18: ~A() (Temporary object destructor)
+// CHECK:    19: b(/*implicit*/(int)0) (Member initializer)
 // CHECK:     Preds (1): B2
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1271,8 +1243,7 @@ const C &bar3(bool coin) {
 // CHECK:     3: [B2.2] (BindTemporary)
 // CHECK:     [[MEMBER:[45]]]: [B2.{{[34]}}].f
 // CHECK:     {{[56]}}: [B2.[[MEMBER]]]()
-// CHECK:     7: (FullExprCleanup collected 1 MTEs)
-// CHECK:     {{[78]}}: ~NoReturn() (Temporary object destructor)
+// CHECK:     {{[67]}}: ~NoReturn() (Temporary object destructor)
 // CHECK:     Preds (1): B3
 // CHECK:     Succs (1): B0
 // CHECK:   [B0 (EXIT)]
@@ -1317,7 +1288,6 @@ const C &bar3(bool coin) {
 // CHECK:     Succs (1): B0
 // CHECK:   [B5]
 // CHECK:     1: [B7.3] || [B6.7]
-// CHECK:     2: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.4]
 // CHECK:     Preds (2): B6 B7
 // CHECK:     Succs (2): B4 B3
@@ -1369,7 +1339,6 @@ const C &bar3(bool coin) {
 // CHECK:     Succs (1): B0
 // CHECK:   [B5]
 // CHECK:     1: [B8.3] || [B7.3] || [B6.7]
-// CHECK:     2: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.4]
 // CHECK:     Preds (3): B6 B7 B8
 // CHECK:     Succs (2): B4 B3
@@ -1428,7 +1397,6 @@ const C &bar3(bool coin) {
 // CHECK:     Succs (1): B0
 // CHECK:   [B5]
 // CHECK:     1: [B8.3] || [B7.2] || [B6.7]
-// CHECK:     2: (FullExprCleanup collected 1 MTEs)
 // CHECK:     T: (Temp Dtor) [B6.4]
 // CHECK:     Preds (3): B6 B7 B8
 // CHECK:     Succs (2): B4(Unreachable) B3

>From 6532e2f263cbc2dd67cbd721eb09fca905b94fec Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Tue, 10 Feb 2026 19:35:35 +1000
Subject: [PATCH 8/9] Ensure correct conditions at call sites of
 VisitForTemporaries

---
 clang/lib/Analysis/CFG.cpp | 64 ++++++++++++++++++++------------------
 1 file changed, 34 insertions(+), 30 deletions(-)

diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 5d70932f7347b..8efbb9589a28a 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -731,19 +731,20 @@ class CFGBuilder {
   // Visitors to walk an AST and generate destructors of temporaries in
   // full expression.
   CFGBlock *VisitForTemporaries(Stmt *E, bool ExternallyDestructed,
-                                   TempDtorContext &Context);
-  CFGBlock *VisitChildrenForTemporaries(Stmt *E,  bool ExternallyDestructed,
-                                           TempDtorContext &Context);
+                                TempDtorContext &Context);
+  CFGBlock *VisitChildrenForTemporaries(Stmt *E, bool ExternallyDestructed,
+                                        TempDtorContext &Context);
   CFGBlock *VisitBinaryOperatorForTemporaries(BinaryOperator *E,
-                                                 bool ExternallyDestructed,
-                                                 TempDtorContext &Context);
+                                              bool ExternallyDestructed,
+                                              TempDtorContext &Context);
   CFGBlock *VisitCXXBindTemporaryExprForTemporaryDtors(
       CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context);
-  CFGBlock *VisitConditionalOperatorForTemporaries(
-      AbstractConditionalOperator *E, bool ExternallyDestructed,
-      TempDtorContext &Context);
+  CFGBlock *
+  VisitConditionalOperatorForTemporaries(AbstractConditionalOperator *E,
+                                         bool ExternallyDestructed,
+                                         TempDtorContext &Context);
   void InsertTempDecisionBlock(const TempDtorContext &Context,
-                                   CFGBlock *FalseSucc = nullptr);
+                               CFGBlock *FalseSucc = nullptr);
 
   // NYS == Not Yet Supported
   CFGBlock *NYS() {
@@ -1828,11 +1829,12 @@ CFGBlock *CFGBuilder::addInitializer(CXXCtorInitializer *I) {
   if (Init) {
     HasTemporaries = isa<ExprWithCleanups>(Init);
 
-    if (BuildOpts.AddTemporaryDtors && HasTemporaries) {
+    if (HasTemporaries &&
+        (BuildOpts.AddTemporaryDtors || BuildOpts.AddLifetime)) {
       // Generate destructors for temporaries in initialization expression.
       TempDtorContext Context;
       VisitForTemporaries(cast<ExprWithCleanups>(Init)->getSubExpr(),
-                             /*ExternallyDestructed=*/false, Context);
+                          /*ExternallyDestructed=*/false, Context);
 
       addFullExprCleanupMarker(Context);
     }
@@ -3143,11 +3145,12 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) {
   if (Init) {
     HasTemporaries = isa<ExprWithCleanups>(Init);
 
-    if (BuildOpts.AddTemporaryDtors && HasTemporaries) {
+    if (HasTemporaries &&
+        (BuildOpts.AddTemporaryDtors || BuildOpts.AddLifetime)) {
       // Generate destructors for temporaries in initialization expression.
       TempDtorContext Context;
       VisitForTemporaries(cast<ExprWithCleanups>(Init)->getSubExpr(),
-                             /*ExternallyDestructed=*/true, Context);
+                          /*ExternallyDestructed=*/true, Context);
 
       addFullExprCleanupMarker(Context);
     }
@@ -4961,7 +4964,7 @@ CFGBlock *CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
 CFGBlock *CFGBuilder::VisitExprWithCleanups(ExprWithCleanups *E,
                                             AddStmtChoice asc,
                                             bool ExternallyDestructed) {
-  if (BuildOpts.AddTemporaryDtors) {
+  if (BuildOpts.AddTemporaryDtors || BuildOpts.AddLifetime) {
     // If adding implicit destructors visit the full expression for adding
     // destructors of temporaries.
     TempDtorContext Context;
@@ -5105,7 +5108,7 @@ CFGBlock *CFGBuilder::VisitIndirectGotoStmt(IndirectGotoStmt *I) {
 }
 
 CFGBlock *CFGBuilder::VisitForTemporaries(Stmt *E, bool ExternallyDestructed,
-                                             TempDtorContext &Context) {
+                                          TempDtorContext &Context) {
 
 tryAgain:
   if (!E) {
@@ -5121,8 +5124,7 @@ CFGBlock *CFGBuilder::VisitForTemporaries(Stmt *E, bool ExternallyDestructed,
 
     case Stmt::BinaryOperatorClass:
       return VisitBinaryOperatorForTemporaries(cast<BinaryOperator>(E),
-                                                  ExternallyDestructed,
-                                                  Context);
+                                               ExternallyDestructed, Context);
 
     case Stmt::CXXBindTemporaryExprClass:
       return VisitCXXBindTemporaryExprForTemporaryDtors(
@@ -5166,7 +5168,7 @@ CFGBlock *CFGBuilder::VisitForTemporaries(Stmt *E, bool ExternallyDestructed,
       // Visit the skipped comma operator left-hand sides for other temporaries.
       for (const Expr *CommaLHS : CommaLHSs) {
         VisitForTemporaries(const_cast<Expr *>(CommaLHS),
-                               /*ExternallyDestructed=*/false, Context);
+                            /*ExternallyDestructed=*/false, Context);
       }
       goto tryAgain;
     }
@@ -5207,21 +5209,22 @@ CFGBlock *CFGBuilder::VisitForTemporaries(Stmt *E, bool ExternallyDestructed,
 }
 
 CFGBlock *CFGBuilder::VisitChildrenForTemporaries(Stmt *E,
-                                                     bool ExternallyDestructed,
-                                                     TempDtorContext &Context) {
+                                                  bool ExternallyDestructed,
+                                                  TempDtorContext &Context) {
   if (isa<LambdaExpr>(E)) {
     // Do not visit the children of lambdas; they have their own CFGs.
     return Block;
   }
 
-  // When visiting children for destructors or lifetime markers we want to visit them in reverse
-  // order that they will appear in the CFG.  Because the CFG is built
-  // bottom-up, this means we visit them in their natural order, which
+  // When visiting children for destructors or lifetime markers we want to visit
+  // them in reverse order that they will appear in the CFG.  Because the CFG is
+  // built bottom-up, this means we visit them in their natural order, which
   // reverses them in the CFG.
   CFGBlock *B = Block;
   for (Stmt *Child : E->children())
     if (Child)
-      if (CFGBlock *R = VisitForTemporaries(Child, ExternallyDestructed, Context))
+      if (CFGBlock *R =
+              VisitForTemporaries(Child, ExternallyDestructed, Context))
         B = R;
 
   return B;
@@ -5233,7 +5236,8 @@ CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaries(
     // For the comma operator, the LHS expression is evaluated before the RHS
     // expression, so prepend temporary destructors for the LHS first.
     CFGBlock *LHSBlock = VisitForTemporaries(E->getLHS(), false, Context);
-    CFGBlock *RHSBlock = VisitForTemporaries(E->getRHS(), ExternallyDestructed, Context);
+    CFGBlock *RHSBlock =
+        VisitForTemporaries(E->getRHS(), ExternallyDestructed, Context);
     return RHSBlock ? RHSBlock : LHSBlock;
   }
 
@@ -5274,9 +5278,8 @@ CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors(
   // First add destructors for temporaries in subexpression.
   // Because VisitCXXBindTemporaryExpr calls setDestructed:
   CFGBlock *B = VisitForTemporaries(E->getSubExpr(), true, Context);
-  if (!ExternallyDestructed && 
-    BuildOpts.AddImplicitDtors && 
-    BuildOpts.AddTemporaryDtors) {
+  if (!ExternallyDestructed && BuildOpts.AddImplicitDtors &&
+      BuildOpts.AddTemporaryDtors) {
     // If lifetime of temporary is not prolonged (by assigning to constant
     // reference) add destructor for it.
 
@@ -5307,7 +5310,7 @@ CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors(
 }
 
 void CFGBuilder::InsertTempDecisionBlock(const TempDtorContext &Context,
-                                             CFGBlock *FalseSucc) {
+                                         CFGBlock *FalseSucc) {
   if (!Context.TerminatorExpr) {
     // If no temporary was found, we do not need to insert a decision point.
     return;
@@ -5339,7 +5342,8 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaries(
 
   Block = ConditionBlock;
   Succ = ConditionSucc;
-  TempDtorContext FalseContext(bothKnownTrue(Context.KnownExecuted, NegatedVal));
+  TempDtorContext FalseContext(
+      bothKnownTrue(Context.KnownExecuted, NegatedVal));
   VisitForTemporaries(E->getFalseExpr(), ExternallyDestructed, FalseContext);
 
   if (TrueContext.TerminatorExpr && FalseContext.TerminatorExpr) {

>From a29467048e7f7b6e088ff0331be5ae09cc2aaba9 Mon Sep 17 00:00:00 2001
From: Abhinav Pradeep <abhinav.pradeep at oracle.com>
Date: Fri, 13 Feb 2026 11:49:16 +1000
Subject: [PATCH 9/9] Addressed review comments

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  3 +-
 clang/lib/Analysis/CFG.cpp                    | 27 ++++++++----
 .../LifetimeSafety/FactsGenerator.cpp         | 43 +++++++++----------
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  2 -
 .../Sema/warn-lifetime-analysis-nocfg.cpp     |  4 +-
 5 files changed, 42 insertions(+), 37 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 4f649d5a19d84..8ef935f62096c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -60,9 +60,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);
 
   void handleCXXCtorInitializer(const CXXCtorInitializer *CII);
-  void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
 
-  void handleFullExprCleanup(const CFGFullExprCleanup &FullExprCleanup);
+  void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
 
   void handleExitBlock();
 
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 8efbb9589a28a..2fd370fe901a3 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -54,6 +54,7 @@
 #include "llvm/Support/SaveAndRestore.h"
 #include "llvm/Support/TimeProfiler.h"
 #include "llvm/Support/raw_ostream.h"
+#include <algorithm>
 #include <cassert>
 #include <cstddef>
 #include <memory>
@@ -717,8 +718,7 @@ class CFGBuilder {
     }
 
     void track(const MaterializeTemporaryExpr *MTE) {
-      if (MTE)
-        CollectedMTEs.push_back(MTE);
+      CollectedMTEs.push_back(MTE);
     }
 
     const bool IsConditional = false;
@@ -2085,7 +2085,6 @@ void CFGBuilder::addScopeChangesHandling(LocalScope::const_iterator SrcPos,
 }
 
 void CFGBuilder::addFullExprCleanupMarker(TempDtorContext &Context) {
-
   CFGFullExprCleanup::MTEVecTy *ExpiringMTEs = nullptr;
   BumpVectorContext &BVC = cfg->getBumpVectorContext();
 
@@ -6041,12 +6040,24 @@ 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)";
+  case CFGElement::Kind::FullExprCleanup: {
+    auto MTEs = E.castAs<CFGFullExprCleanup>().getExpiringMTEs();
+    size_t MTECount = MTEs.size();
+    OS << "(FullExprCleanup collected " << MTECount
+       << (MTECount > 1 ? " MTEs: " : " MTE: ");
+    bool FirstMTE = true;
+    for (const MaterializeTemporaryExpr *MTE : MTEs) {
+      if (!FirstMTE)
+        OS << ", ";
+      if (!Helper.handledStmt(MTE->getSubExpr(), OS)) {
+        // Pretty print the sub-expresion as a fallback
+        MTE->printPretty(OS, &Helper, PrintingPolicy(Helper.getLangOpts()));
+      };
+      FirstMTE = false;
+    }
+    OS << ")";
     break;
+  }
 
   case CFGElement::Kind::LoopExit:
     OS << E.castAs<CFGLoopExit>().getLoopStmt()->getStmtClassName()
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 4e42e674ac4b4..58c5afc0af9d9 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -108,8 +108,26 @@ void FactsGenerator::run() {
                    Element.getAs<CFGLifetimeEnds>())
         handleLifetimeEnds(*LifetimeEnds);
       else if (std::optional<CFGFullExprCleanup> FullExprCleanup =
-                   Element.getAs<CFGFullExprCleanup>())
-        handleFullExprCleanup(*FullExprCleanup);
+                   Element.getAs<CFGFullExprCleanup>()) {
+        ArrayRef<const MaterializeTemporaryExpr *> CollectedMTEs =
+            FullExprCleanup->getExpiringMTEs();
+        // Iterate through all loans to see if any expire.
+        for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
+          if (const auto *PL = dyn_cast<PathLoan>(Loan)) {
+            // Check if the loan is for a temporary materialization and if that
+            // storage location is the one being destructed.
+            const AccessPath &AP = PL->getAccessPath();
+            const MaterializeTemporaryExpr *Path =
+                AP.getAsMaterializeTemporaryExpr();
+            if (!Path)
+              continue;
+            if (llvm::is_contained(CollectedMTEs, Path)) {
+              CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
+                  PL->getID(), Path->getEndLoc()));
+            }
+          }
+        }
+      }
     }
     if (Block == &Cfg.getExit())
       handleExitBlock();
@@ -461,27 +479,6 @@ void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
   }
 }
 
-void FactsGenerator::handleFullExprCleanup(
-    const CFGFullExprCleanup &FullExprCleanup) {
-  ArrayRef<const MaterializeTemporaryExpr *> CollectedMTEs =
-      FullExprCleanup.getExpiringMTEs();
-  // Iterate through all loans to see if any expire.
-  for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
-    if (const auto *PL = dyn_cast<PathLoan>(Loan)) {
-      // Check if the loan is for a temporary materialization and if that
-      // storage location is the one being destructed.
-      const AccessPath &AP = PL->getAccessPath();
-      const MaterializeTemporaryExpr *Path = AP.getAsMaterializeTemporaryExpr();
-      if (!Path)
-        continue;
-      if (is_contained(CollectedMTEs, Path)) {
-        CurrentBlockFacts.push_back(
-            FactMgr.createFact<ExpireFact>(PL->getID(), Path->getEndLoc()));
-      }
-    }
-  }
-}
-
 void FactsGenerator::handleExitBlock() {
   // Creates FieldEscapeFacts for all field origins that remain live at exit.
   for (const Origin &O : FactMgr.getOriginMgr().getOrigins())
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0c96b0afef1a7..ff22c3934007f 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2999,8 +2999,6 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
     AC.getCFGBuildOptions().PruneTriviallyFalseEdges = false;
     AC.getCFGBuildOptions().AddLifetime = true;
     AC.getCFGBuildOptions().AddParameterLifetimes = true;
-    AC.getCFGBuildOptions().AddImplicitDtors = true;
-    AC.getCFGBuildOptions().AddTemporaryDtors = true;
     AC.getCFGBuildOptions().setAllAlwaysAdd();
     if (AC.getCFG())
       runLifetimeSafetyAnalysis(AC, &SemaHelper, LSStats, S.CollectStats);
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index e06b08543ed6b..4e52c407a3137 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -298,11 +298,11 @@ std::string_view danglingRefToOptionalFromTemp4() {
 void danglingReferenceFromTempOwner() {
   int &&r = *std::optional<int>();          // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
                                             // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
-  //        https://github.com/llvm/llvm-project/issues/175893
+  //https://github.com/llvm/llvm-project/issues/175893
   int &&r2 = *std::optional<int>(5);        // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
                                               // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
 
-  //        https://github.com/llvm/llvm-project/issues/175893
+  //https://github.com/llvm/llvm-project/issues/175893
   int &&r3 = std::optional<int>(5).value(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
                                               // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
 



More information about the cfe-commits mailing list