https://github.com/AbhinavPradeep created https://github.com/llvm/llvm-project/pull/177985
So far: 1. Added `CFGFullExprCleanup` which has a pointer to `BumpVector<const MaterializeTemporaryExpr *>` to track all MTE that **might** (in the sense that we take union on branches) be spawned by an `ExprWithCleanups` 2. Modified logic in `CFGBuilder` to appropriately insert this marker. It inserts the marker primarily via `CFGBuilder::VisitExprWithCleanups`, and also `CFGBuilder::addInitializer` and `CFGBuilder::VisitDeclSubExpr` as these bypass visiting the `ExprWithCleanups`. The bump vector is allocated appropriately using the bump allocator of the CFG to respect its lifetime rules. 3. Visiting to track the temporaries is done in `CFGBuilder::VisitForTemporaryDtors`. We introduce a flag to `TempDtorContext` so as to enable tracking only when necessary. The encountered non-lifetime extended MTE are stored in a small vector member of `TempDtorContext`. >From 8bae199a384392630a2189467c5be254b9abc05b Mon Sep 17 00:00:00 2001 From: Abhinav Pradeep <[email protected]> Date: Tue, 27 Jan 2026 01:20:55 +1000 Subject: [PATCH] Modify CFG to have a CFGFullExprCleanup marker. --- clang/include/clang/Analysis/CFG.h | 32 ++++++++++++ clang/lib/Analysis/CFG.cpp | 71 ++++++++++++++++++++++++--- clang/lib/Analysis/PathDiagnostic.cpp | 1 + 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index a4bafd4927df0..c021ffcf85785 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -62,6 +62,7 @@ class CFGElement { NewAllocator, LifetimeEnds, LoopExit, + FullExprCleanup, // stmt kind Statement, Constructor, @@ -313,6 +314,32 @@ class CFGLifetimeEnds : public CFGElement { } }; +class CFGFullExprCleanup : public CFGElement { + using MTEVecTy = BumpVector<const MaterializeTemporaryExpr *>; + +public: + explicit CFGFullExprCleanup(const MTEVecTy *vec) + : CFGElement(FullExprCleanup, vec, nullptr) {} + + ArrayRef<const MaterializeTemporaryExpr *> getExpiringMTEs() const { + const MTEVecTy *ExpiringMTEs = + static_cast<const MTEVecTy *>(Data1.getPointer()); + if (!ExpiringMTEs) + return {}; + return ArrayRef<const MaterializeTemporaryExpr *>(ExpiringMTEs->begin(), + ExpiringMTEs->end()); + } + +private: + friend class CFGElement; + + CFGFullExprCleanup() = default; + + static bool isKind(const CFGElement &elem) { + return elem.getKind() == FullExprCleanup; + } +}; + /// Represents beginning of a scope implicitly generated /// by the compiler on encountering a CompoundStmt class CFGScopeBegin : public CFGElement { @@ -1183,6 +1210,11 @@ class CFGBlock { Elements.push_back(CFGLifetimeEnds(VD, S), C); } + void appendFullExprCleanup(BumpVector<const MaterializeTemporaryExpr *> *BV, + BumpVectorContext &C) { + Elements.push_back(CFGFullExprCleanup(BV), C); + } + void appendLoopExit(const Stmt *LoopStmt, BumpVectorContext &C) { Elements.push_back(CFGLoopExit(LoopStmt), C); } diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index a9c7baa00543c..03b3fd7efe89e 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -55,6 +55,7 @@ #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/raw_ostream.h" #include <cassert> +#include <cstddef> #include <memory> #include <optional> #include <string> @@ -698,7 +699,9 @@ class CFGBuilder { TempDtorContext() = default; TempDtorContext(TryResult KnownExecuted) : IsConditional(true), KnownExecuted(KnownExecuted) {} - + TempDtorContext(TryResult KnownExecuted, bool TrackExpiringMTEs) + : IsConditional(true), TrackExpiringMTEs(TrackExpiringMTEs), + KnownExecuted(KnownExecuted) {} /// Returns whether we need to start a new branch for a temporary destructor /// call. This is the case when the temporary destructor is /// conditionally executed, and it is the first one we encounter while @@ -716,7 +719,16 @@ class CFGBuilder { TerminatorExpr = E; } + void track(const MaterializeTemporaryExpr *MTE) { + // Must only be invoked when TrackMTE is true + assert(TrackExpiringMTEs); + if (MTE) + CollectedMTEs.push_back(MTE); + } + const bool IsConditional = false; + bool TrackExpiringMTEs = false; + SmallVector<const MaterializeTemporaryExpr *, 5> CollectedMTEs; const TryResult KnownExecuted = true; CFGBlock *Succ = nullptr; CXXBindTemporaryExpr *TerminatorExpr = nullptr; @@ -803,6 +815,7 @@ class CFGBuilder { void addScopeChangesHandling(LocalScope::const_iterator SrcPos, LocalScope::const_iterator DstPos, Stmt *S); + void addFullExprCleanupMarker(TempDtorContext &Context); CFGBlock *createScopeChangesHandlingBlock(LocalScope::const_iterator SrcPos, CFGBlock *SrcBlk, LocalScope::const_iterator DstPost, @@ -1816,8 +1829,11 @@ CFGBlock *CFGBuilder::addInitializer(CXXCtorInitializer *I) { if (BuildOpts.AddTemporaryDtors && HasTemporaries) { // Generate destructors for temporaries in initialization expression. TempDtorContext Context; + Context.TrackExpiringMTEs = true; VisitForTemporaryDtors(cast<ExprWithCleanups>(Init)->getSubExpr(), /*ExternallyDestructed=*/false, Context); + + addFullExprCleanupMarker(Context); } } @@ -2065,6 +2081,23 @@ void CFGBuilder::addScopeChangesHandling(LocalScope::const_iterator SrcPos, addAutomaticObjHandling(SrcPos, BasePos, S); } +void CFGBuilder::addFullExprCleanupMarker(TempDtorContext &Context) { + assert(Context.TrackExpiringMTEs); + + using MTEVecTy = BumpVector<const MaterializeTemporaryExpr *>; + MTEVecTy *ExpiringMTEs = nullptr; + BumpVectorContext &BVC = cfg->getBumpVectorContext(); + + size_t NumCollected = Context.CollectedMTEs.size(); + if (NumCollected > 0) { + autoCreateBlock(); + ExpiringMTEs = new (cfg->getAllocator()) MTEVecTy(BVC, NumCollected); + for (const MaterializeTemporaryExpr *MTE : Context.CollectedMTEs) + ExpiringMTEs->push_back(MTE, BVC); + Block->appendFullExprCleanup(ExpiringMTEs, BVC); + } +} + /// createScopeChangesHandlingBlock - Creates a block with cfgElements /// corresponding to changing the scope from the source scope of the GotoStmt, /// to destination scope. Add destructor, lifetime and cfgScopeEnd @@ -3113,8 +3146,11 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) { if (BuildOpts.AddTemporaryDtors && HasTemporaries) { // Generate destructors for temporaries in initialization expression. TempDtorContext Context; + Context.TrackExpiringMTEs = true; VisitForTemporaryDtors(cast<ExprWithCleanups>(Init)->getSubExpr(), /*ExternallyDestructed=*/true, Context); + + addFullExprCleanupMarker(Context); } } @@ -4924,13 +4960,17 @@ CFGBlock *CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) { } CFGBlock *CFGBuilder::VisitExprWithCleanups(ExprWithCleanups *E, - AddStmtChoice asc, bool ExternallyDestructed) { + AddStmtChoice asc, + bool ExternallyDestructed) { if (BuildOpts.AddTemporaryDtors) { // If adding implicit destructors visit the full expression for adding // destructors of temporaries. TempDtorContext Context; + Context.TrackExpiringMTEs = true; VisitForTemporaryDtors(E->getSubExpr(), ExternallyDestructed, Context); + addFullExprCleanupMarker(Context); + // Full expression has to be added as CFGStmt so it will be sequenced // before destructors of it's temporaries. asc = asc.withAlwaysAdd(true); @@ -5117,6 +5157,8 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed, case Stmt::MaterializeTemporaryExprClass: { const MaterializeTemporaryExpr* MTE = cast<MaterializeTemporaryExpr>(E); ExternallyDestructed = (MTE->getStorageDuration() != SD_FullExpression); + if (Context.TrackExpiringMTEs && !ExternallyDestructed) + Context.track(MTE); SmallVector<const Expr *, 2> CommaLHSs; SmallVector<SubobjectAdjustment, 2> Adjustments; // Find the expression whose lifetime needs to be extended. @@ -5208,10 +5250,14 @@ CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors( // executed, thus we add a branch node that depends on the temporary // constructor call. TempDtorContext RHSContext( - bothKnownTrue(Context.KnownExecuted, RHSExecuted)); + bothKnownTrue(Context.KnownExecuted, RHSExecuted), + Context.TrackExpiringMTEs); VisitForTemporaryDtors(E->getRHS(), false, RHSContext); InsertTempDtorDecisionBlock(RHSContext); + if (Context.TrackExpiringMTEs) + Context.CollectedMTEs.append(RHSContext.CollectedMTEs); + return Block; } @@ -5290,14 +5336,15 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors( if (NegatedVal.isKnown()) NegatedVal.negate(); TempDtorContext TrueContext( - bothKnownTrue(Context.KnownExecuted, ConditionVal)); + bothKnownTrue(Context.KnownExecuted, ConditionVal), + Context.TrackExpiringMTEs); VisitForTemporaryDtors(E->getTrueExpr(), ExternallyDestructed, TrueContext); CFGBlock *TrueBlock = Block; Block = ConditionBlock; Succ = ConditionSucc; - TempDtorContext FalseContext( - bothKnownTrue(Context.KnownExecuted, NegatedVal)); + TempDtorContext FalseContext(bothKnownTrue(Context.KnownExecuted, NegatedVal), + Context.TrackExpiringMTEs); VisitForTemporaryDtors(E->getFalseExpr(), ExternallyDestructed, FalseContext); if (TrueContext.TerminatorExpr && FalseContext.TerminatorExpr) { @@ -5308,6 +5355,11 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors( } else { InsertTempDtorDecisionBlock(FalseContext); } + if (Context.TrackExpiringMTEs) { + Context.CollectedMTEs.append(TrueContext.CollectedMTEs); + Context.CollectedMTEs.append(FalseContext.CollectedMTEs); + } + return Block; } @@ -5989,6 +6041,13 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, OS << " (Lifetime ends)"; break; + case CFGElement::Kind::FullExprCleanup: + OS << "(FullExprCleanup collected " + << std::to_string( + E.castAs<CFGFullExprCleanup>().getExpiringMTEs().size()) + << " MTEs)"; + break; + case CFGElement::Kind::LoopExit: OS << E.castAs<CFGLoopExit>().getLoopStmt()->getStmtClassName() << " (LoopExit)"; diff --git a/clang/lib/Analysis/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp index e42731b93bfb2..5e387e086a5a7 100644 --- a/clang/lib/Analysis/PathDiagnostic.cpp +++ b/clang/lib/Analysis/PathDiagnostic.cpp @@ -564,6 +564,7 @@ getLocationForCaller(const StackFrameContext *SFC, case CFGElement::CleanupFunction: llvm_unreachable("not yet implemented!"); case CFGElement::LifetimeEnds: + case CFGElement::FullExprCleanup: case CFGElement::LoopExit: llvm_unreachable("CFGElement kind should not be on callsite!"); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
