[clang] [NFC][analyzer] Improve tracking of the current Block & LocCtx (PR #185107)

Donát Nagy via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 6 13:01:05 PST 2026


https://github.com/NagyDonat created https://github.com/llvm/llvm-project/pull/185107

During the analysis the _current_ (currently analyzed) `CFGBlock` and `LocationContext` (≈ stack frame) need to be easily accessible from the context because these are often needed "deep" within the call stack e.g. to query the "block count" and assign a unique identifier to a conjured symbol.

Before this commit, this information was accessed via the field `const NodeBuilderContext *currBldrCtx` of `ExprEngine` which usually points to a `NodeBuilderContext` instantiated as a local variable in some random method of the engine.

Instead of this, I'm trying to gradually move towards an ideal state where the current `CFGBlock` and `LocationContext` would be stored in two "natural" data members of `ExprEngine`, e.g. as
```c++
const LocationContext *CurrLocationContext;
const CFGBlock *CurrBlock;
```

To prepare the ground for this, I'm introducing new methods in `ExprEngine` that currently use `currBldrCtx`, but will be able to switch to a different "backend" in the future.

This PR is just a part of this journey -- I will follow it up with more changes.

Note that as of now there are some situations where the "current" `CFGBlock` and `LocationContext` (which are currently stored in `currBldrCtx` and used primarily to query the block count) are inconsistent with other sources of the same information (e.g. the `LocationContext` of the exploded nodes). These inconsistencies are not touched by this PR (which is NFC), but the centralization of handling the current `CFGBlock` and `LocationContext` will be helpful to eliminate these as well.

-----

Note for reviewers: This PR is split into many small commits; each of those does one thing and the more complex ones have explanations in the commit messages.

I'm open to merge this PR in multiple commits if you would prefer that.

>From ad055230fe514202b9cec9067132518a4f5075a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 15:19:43 +0100
Subject: [PATCH 01/11] [NFC][analyzer] Add ExprEngine::getCurrentBlock()

This utility function will be useful in the future
---
 .../clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h   | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 2ca03174fbdc9..9b87b14f31d87 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -226,9 +226,12 @@ class ExprEngine {
     return G.getRoot()->getLocation().getLocationContext();
   }
 
+  const CFGBlock *getCurrentBlock() const {
+    return currBldrCtx ? currBldrCtx->getBlock() : nullptr;
+  }
+
   ConstCFGElementRef getCFGElementRef() const {
-    const CFGBlock *blockPtr = currBldrCtx ? currBldrCtx->getBlock() : nullptr;
-    return {blockPtr, currStmtIdx};
+    return {getCurrentBlock(), currStmtIdx};
   }
 
   /// Dump graph to the specified filename.

>From 59b5dffed1dbc3823ea0c5e3b391d3b9b8005013 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 15:29:47 +0100
Subject: [PATCH 02/11] [NFC][analyzer] Apply ExprEngine::getCurrentBlock()

---
 .../StaticAnalyzer/Core/PathSensitive/ExprEngine.h |  2 +-
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp       | 14 +++++++-------
 .../Core/ExprEngineCallAndReturn.cpp               |  2 +-
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 9b87b14f31d87..84c16c81e748d 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -710,7 +710,7 @@ class ExprEngine {
   /// Return the CFG element corresponding to the worklist element
   /// that is currently being processed by ExprEngine.
   CFGElement getCurrentCFGElement() {
-    return (*currBldrCtx->getBlock())[currStmtIdx];
+    return (*getCurrentBlock())[currStmtIdx];
   }
 
   /// Create a new state in which the call return value is binded to the
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index bc8e9040444c9..53e311cfc7141 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1144,7 +1144,7 @@ void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) {
   }
 
   // Enqueue the new nodes onto the work list.
-  Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx);
+  Engine.enqueueStmtNodes(Dst, getCurrentBlock(), currStmtIdx);
 }
 
 void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) {
@@ -1159,7 +1159,7 @@ void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) {
   LoopExit PP(S, Pred->getLocationContext());
   ExplodedNode *N = Engine.makeNode(PP, NewState, Pred);
   if (N && !N->isSink())
-    Engine.enqueueStmtNode(N, currBldrCtx->getBlock(), currStmtIdx);
+    Engine.enqueueStmtNode(N, getCurrentBlock(), currStmtIdx);
 }
 
 void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
@@ -1248,7 +1248,7 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
   for (ExplodedNode *Pred : Tmp)
     Dst.Add(Engine.makeNode(PP, Pred->getState(), Pred));
   // Enqueue the new nodes onto the work list.
-  Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx);
+  Engine.enqueueStmtNodes(Dst, getCurrentBlock(), currStmtIdx);
 }
 
 std::pair<ProgramStateRef, uint64_t>
@@ -1312,7 +1312,7 @@ void ExprEngine::ProcessImplicitDtor(const CFGImplicitDtor D,
   }
 
   // Enqueue the new nodes onto the work list.
-  Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx);
+  Engine.enqueueStmtNodes(Dst, getCurrentBlock(), currStmtIdx);
 }
 
 void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE,
@@ -1331,7 +1331,7 @@ void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE,
                         getCFGElementRef());
     Dst.Add(Engine.makeNode(PP, Pred->getState(), Pred));
   }
-  Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx);
+  Engine.enqueueStmtNodes(Dst, getCurrentBlock(), currStmtIdx);
 }
 
 void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor,
@@ -1844,7 +1844,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::OMPMetaDirectiveClass:
     case Stmt::HLSLOutArgExprClass: {
       const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState());
-      Engine.addAbortedBlock(node, currBldrCtx->getBlock());
+      Engine.addAbortedBlock(node, getCurrentBlock());
       break;
     }
 
@@ -2120,7 +2120,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
         Bldr.addNodes(Dst);
       } else {
         const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState());
-        Engine.addAbortedBlock(node, currBldrCtx->getBlock());
+        Engine.addAbortedBlock(node, getCurrentBlock());
       }
       break;
 
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index a407a923d7d0f..0e101af2cdf25 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -551,7 +551,7 @@ void ExprEngine::inlineCall(WorkList *WList, const CallEvent &Call,
   // Construct a new stack frame for the callee.
   AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D);
   const StackFrameContext *CalleeSFC =
-      CalleeADC->getStackFrame(ParentOfCallee, CallE, currBldrCtx->getBlock(),
+      CalleeADC->getStackFrame(ParentOfCallee, CallE, getCurrentBlock(),
                                currBldrCtx->blockCount(), currStmtIdx);
 
   CallEnter Loc(CallE, CalleeSFC, CurLC);

>From cf3ab6e9b9b1e30fc52c2eb594194f68b1f77bfc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 15:21:35 +0100
Subject: [PATCH 03/11] [NFC][analyzer] Add ExprEngine::getNumVisited()

---
 .../clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h    | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 84c16c81e748d..020c339b1b4c8 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -234,6 +234,12 @@ class ExprEngine {
     return {getCurrentBlock(), currStmtIdx};
   }
 
+  unsigned getNumVisited(const LocationContext *LC,
+                         const CFGBlock *Block) const {
+    return Engine.WList->getBlockCounter().getNumVisited(LC->getStackFrame(),
+                                                         Block->getBlockID());
+  }
+
   /// Dump graph to the specified filename.
   /// If filename is empty, generate a temporary one.
   /// \return The filename the graph is written into.

>From 54b0c007ec72fb33defa5b181923d9cd0cc633cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 15:35:20 +0100
Subject: [PATCH 04/11] [NFC][analyzer] Add
 ExprEngine::getCurrentLocationContext()

---
 .../clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h      | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 020c339b1b4c8..926f11fd74973 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -226,6 +226,10 @@ class ExprEngine {
     return G.getRoot()->getLocation().getLocationContext();
   }
 
+  const LocationContext *getCurrentLocationContext() const {
+    return currBldrCtx ? currBldrCtx->getLocationContext() : nullptr;
+  }
+
   const CFGBlock *getCurrentBlock() const {
     return currBldrCtx ? currBldrCtx->getBlock() : nullptr;
   }

>From 6b6bfe1bf7ad1949bc2d52699f5d65ee51be5409 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 15:41:51 +0100
Subject: [PATCH 05/11] [NFC][analyzer] Add ExprEngine::getNumVisitedCurrent()

This is exactly equivalent to `currBldrCtx->blockCount()` but prepares
the ground for the eventual elimination of `currBldrCtx`.

The block count (i.e. how many times was the block visited within the
stack frame) has nothing to do with building exploded nodes so I'm
trying to distance them from the types `NodeBuilder` and
`NodeBuilderContext`.
---
 .../clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h      | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 926f11fd74973..d37f33da71ab9 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -244,6 +244,10 @@ class ExprEngine {
                                                          Block->getBlockID());
   }
 
+  unsigned getNumVisitedCurrent() const {
+    return getNumVisited(getCurrentLocationContext(), getCurrentBlock());
+  }
+
   /// Dump graph to the specified filename.
   /// If filename is empty, generate a temporary one.
   /// \return The filename the graph is written into.

>From b33ddc0a67bf981d589c2236f0c1db01eb8d6c72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 15:45:18 +0100
Subject: [PATCH 06/11] [NFC][analyzer] Apply
 ExprEngine::getNumVisitedCurrent()

This replaces many references to `currBldrCtx`.

Most of the remaining references construct short-lived `NodeBuilder`
objects which are superfluous -- I intend to replace their use with
direct calls to `CoreEngine::makeNode`.
---
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp  | 16 ++++++-------
 clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 23 +++++++++----------
 .../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp |  6 ++---
 .../Core/ExprEngineCallAndReturn.cpp          |  6 ++---
 .../StaticAnalyzer/Core/ExprEngineObjC.cpp    | 11 +++++----
 5 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 53e311cfc7141..1c5b968734e89 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -422,7 +422,7 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
     case SubobjectAdjustment::MemberPointerAdjustment:
       // FIXME: Unimplemented.
       State = State->invalidateRegions(Reg, getCFGElementRef(),
-                                       currBldrCtx->blockCount(), LC, true,
+                                       getNumVisitedCurrent(), LC, true,
                                        nullptr, nullptr, nullptr);
       return State;
     }
@@ -439,7 +439,7 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
   SVal InitVal = State->getSVal(Init, LC);
   if (InitVal.isUnknown()) {
     InitVal = getSValBuilder().conjureSymbolVal(
-        getCFGElementRef(), LC, Init->getType(), currBldrCtx->blockCount());
+        getCFGElementRef(), LC, Init->getType(), getNumVisitedCurrent());
     State = State->bindLoc(BaseReg.castAs<Loc>(), InitVal, LC, false);
 
     // Then we'd need to take the value that certainly exists and bind it
@@ -449,7 +449,7 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
       // compute the value.
       InitValWithAdjustments = getSValBuilder().conjureSymbolVal(
           getCFGElementRef(), LC, InitWithAdjustments->getType(),
-          currBldrCtx->blockCount());
+          getNumVisitedCurrent());
     }
     State =
         State->bindLoc(Reg.castAs<Loc>(), InitValWithAdjustments, LC, false);
@@ -1217,7 +1217,7 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
           SValBuilder &SVB = getSValBuilder();
           InitVal =
               SVB.conjureSymbolVal(getCFGElementRef(), stackFrame,
-                                   Field->getType(), currBldrCtx->blockCount());
+                                   Field->getType(), getNumVisitedCurrent());
         }
       } else {
         InitVal = State->getSVal(BMI->getInit(), stackFrame);
@@ -2052,7 +2052,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
         const LocationContext *LCtx = N->getLocationContext();
         SVal result = svalBuilder.conjureSymbolVal(
             /*symbolTag=*/nullptr, getCFGElementRef(), LCtx, resultType,
-            currBldrCtx->blockCount());
+            getNumVisitedCurrent());
         ProgramStateRef State = N->getState()->BindExpr(Ex, LCtx, result);
 
         // Escape pointers passed into the list, unless it's an ObjC boxed
@@ -3601,7 +3601,7 @@ void ExprEngine::VisitAtomicExpr(const AtomicExpr *AE, ExplodedNode *Pred,
     }
 
     State = State->invalidateRegions(ValuesToInvalidate, getCFGElementRef(),
-                                     currBldrCtx->blockCount(), LCtx,
+                                     getNumVisitedCurrent(), LCtx,
                                      /*CausedByPointerEscape*/ true,
                                      /*Symbols=*/nullptr);
 
@@ -3950,7 +3950,7 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
 
     if (std::optional<Loc> LV = X.getAs<Loc>())
       state = state->invalidateRegions(*LV, getCFGElementRef(),
-                                       currBldrCtx->blockCount(),
+                                       getNumVisitedCurrent(),
                                        Pred->getLocationContext(),
                                        /*CausedByPointerEscape=*/true);
   }
@@ -3961,7 +3961,7 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
 
     if (std::optional<Loc> LV = X.getAs<Loc>())
       state = state->invalidateRegions(*LV, getCFGElementRef(),
-                                       currBldrCtx->blockCount(),
+                                       getNumVisitedCurrent(),
                                        Pred->getLocationContext(),
                                        /*CausedByPointerEscape=*/true);
   }
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index b7fac30500d26..67beed5dbb6fb 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -65,7 +65,7 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B,
       // EXPERIMENTAL: "Conjured" symbols.
       // FIXME: Handle structs.
       if (RightV.isUnknown()) {
-        unsigned Count = currBldrCtx->blockCount();
+        unsigned Count = getNumVisitedCurrent();
         RightV = svalBuilder.conjureSymbolVal(nullptr, getCFGElementRef(), LCtx,
                                               Count);
       }
@@ -83,7 +83,7 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B,
       if (B->isAdditiveOp()) {
         // TODO: This can be removed after we enable history tracking with
         // SymSymExpr.
-        unsigned Count = currBldrCtx->blockCount();
+        unsigned Count = getNumVisitedCurrent();
         RightV = conjureOffsetSymbolOnLocation(
             RightV, LeftV, getCFGElementRef(), RHS->getType(), svalBuilder,
             Count, LCtx);
@@ -170,7 +170,7 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B,
         // LValue on the LHS will bind to.
         LHSVal = svalBuilder.conjureSymbolVal(/*symbolTag=*/nullptr,
                                               getCFGElementRef(), LCtx, LTy,
-                                              currBldrCtx->blockCount());
+                                              getNumVisitedCurrent());
         // However, we need to convert the symbol to the computation type.
         Result = svalBuilder.evalCast(LHSVal, CTy, LTy);
       } else {
@@ -201,9 +201,8 @@ void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred,
 
   const BlockDecl *BD = BE->getBlockDecl();
   // Get the value of the block itself.
-  SVal V = svalBuilder.getBlockPointer(BD, T,
-                                       Pred->getLocationContext(),
-                                       currBldrCtx->blockCount());
+  SVal V = svalBuilder.getBlockPointer(BD, T, Pred->getLocationContext(),
+                                       getNumVisitedCurrent());
 
   ProgramStateRef State = Pred->getState();
 
@@ -498,7 +497,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex,
           if (val.isUnknown()) {
             DefinedOrUnknownSVal NewSym = svalBuilder.conjureSymbolVal(
                 /*symbolTag=*/nullptr, getCFGElementRef(), LCtx, resultType,
-                currBldrCtx->blockCount());
+                getNumVisitedCurrent());
             state = state->BindExpr(CastE, LCtx, NewSym);
           } else
             // Else, bind to the derived region value.
@@ -522,7 +521,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex,
         if (val.isUnknown()) {
           val = svalBuilder.conjureSymbolVal(
               /*symbolTag=*/nullptr, getCFGElementRef(), LCtx, resultType,
-              currBldrCtx->blockCount());
+              getNumVisitedCurrent());
         }
         state = state->BindExpr(CastE, LCtx, val);
         Bldr.generateNode(CastE, Pred, state);
@@ -568,7 +567,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex,
           resultType = getContext().getPointerType(resultType);
         SVal result = svalBuilder.conjureSymbolVal(
             /*symbolTag=*/nullptr, getCFGElementRef(), LCtx, resultType,
-            currBldrCtx->blockCount());
+            getNumVisitedCurrent());
         state = state->BindExpr(CastE, LCtx, result);
         Bldr.generateNode(CastE, Pred, state);
         continue;
@@ -661,7 +660,7 @@ void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred,
 
           InitVal = svalBuilder.conjureSymbolVal(
               /*symbolTag=*/nullptr, getCFGElementRef(), LC, Ty,
-              currBldrCtx->blockCount());
+              getNumVisitedCurrent());
         }
 
 
@@ -831,7 +830,7 @@ void ExprEngine::VisitGuardedExpr(const Expr *Ex,
 
   if (!hasValue)
     V = svalBuilder.conjureSymbolVal(nullptr, getCFGElementRef(), LCtx,
-                                     currBldrCtx->blockCount());
+                                     getNumVisitedCurrent());
 
   // Generate a new node with the binding from the appropriate path.
   B.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V, true));
@@ -1115,7 +1114,7 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U,
     if (Result.isUnknown()){
       DefinedOrUnknownSVal SymVal = svalBuilder.conjureSymbolVal(
           /*symbolTag=*/nullptr, getCFGElementRef(), LCtx,
-          currBldrCtx->blockCount());
+          getNumVisitedCurrent());
       Result = SymVal;
 
       // If the value is a location, ++/-- should always preserve
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index 07852984e4ee3..0866dda766667 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -254,7 +254,7 @@ SVal ExprEngine::computeObjectUnderConstruction(
         QualType ReturnTy = RetE->getType();
         QualType RegionTy = ACtx.getPointerType(ReturnTy);
         return SVB.conjureSymbolVal(&TopLevelSymRegionTag, getCFGElementRef(),
-                                    SFC, RegionTy, currBldrCtx->blockCount());
+                                    SFC, RegionTy, getNumVisitedCurrent());
       }
       llvm_unreachable("Unhandled return value construction context!");
     }
@@ -975,7 +975,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
   // really part of the CXXNewExpr because they happen BEFORE the
   // CXXConstructExpr subexpression. See PR12014 for some discussion.
 
-  unsigned blockCount = currBldrCtx->blockCount();
+  unsigned blockCount = getNumVisitedCurrent();
   const LocationContext *LCtx = Pred->getLocationContext();
   SVal symVal = UnknownVal();
   FunctionDecl *FD = CNE->getOperatorNew();
@@ -1138,7 +1138,7 @@ void ExprEngine::VisitCXXCatchStmt(const CXXCatchStmt *CS, ExplodedNode *Pred,
 
   const LocationContext *LCtx = Pred->getLocationContext();
   SVal V = svalBuilder.conjureSymbolVal(getCFGElementRef(), LCtx, VD->getType(),
-                                        currBldrCtx->blockCount());
+                                        getNumVisitedCurrent());
   ProgramStateRef state = Pred->getState();
   state = state->bindLoc(state->getLValue(VD, LCtx), V, LCtx);
 
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 0e101af2cdf25..312eca02e661a 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -552,7 +552,7 @@ void ExprEngine::inlineCall(WorkList *WList, const CallEvent &Call,
   AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D);
   const StackFrameContext *CalleeSFC =
       CalleeADC->getStackFrame(ParentOfCallee, CallE, getCurrentBlock(),
-                               currBldrCtx->blockCount(), currStmtIdx);
+                               getNumVisitedCurrent(), currStmtIdx);
 
   CallEnter Loc(CallE, CalleeSFC, CurLC);
 
@@ -768,7 +768,7 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call,
 
   SVal R;
   QualType ResultTy = Call.getResultType();
-  unsigned Count = currBldrCtx->blockCount();
+  unsigned Count = getNumVisitedCurrent();
   if (auto RTC = getCurrentCFGElement().getAs<CFGCXXRecordTypedCall>()) {
     // Conjure a temporary if the function returns an object by value.
     SVal Target;
@@ -833,7 +833,7 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call,
 // a conjured return value.
 void ExprEngine::conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr,
                                       ExplodedNode *Pred, ProgramStateRef State) {
-  State = Call.invalidateRegions(currBldrCtx->blockCount(), State);
+  State = Call.invalidateRegions(getNumVisitedCurrent(), State);
   State = bindReturnValue(Call, Pred->getLocationContext(), State);
 
   // And make the result node.
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index bb0fd6ded2def..07703bdf38239 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -48,7 +48,7 @@ static void populateObjCForDestinationSet(ExplodedNodeSet &dstLocation,
                                           const ObjCForCollectionStmt *S,
                                           ConstCFGElementRef elem,
                                           SVal elementV, SymbolManager &SymMgr,
-                                          const NodeBuilderContext *currBldrCtx,
+                                          unsigned NumVisitedCurrent,
                                           NodeBuilder &Bldr, bool hasElements) {
 
   for (ExplodedNode *Pred : dstLocation) {
@@ -69,7 +69,7 @@ static void populateObjCForDestinationSet(ExplodedNodeSet &dstLocation,
         SVal V;
         if (hasElements) {
           SymbolRef Sym =
-              SymMgr.conjureSymbol(elem, LCtx, T, currBldrCtx->blockCount());
+              SymMgr.conjureSymbol(elem, LCtx, T, NumVisitedCurrent);
           V = svalBuilder.makeLoc(Sym);
         } else {
           V = svalBuilder.makeIntVal(0, T);
@@ -136,12 +136,13 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S,
 
     if (!isContainerNull)
       populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S,
-                                    elemRef, elementV, SymMgr, currBldrCtx,
-                                    Bldr,
+                                    elemRef, elementV, SymMgr,
+                                    getNumVisitedCurrent(), Bldr,
                                     /*hasElements=*/true);
 
     populateObjCForDestinationSet(DstLocationSingleton, svalBuilder, S, elemRef,
-                                  elementV, SymMgr, currBldrCtx, Bldr,
+                                  elementV, SymMgr, getNumVisitedCurrent(),
+                                  Bldr,
                                   /*hasElements=*/false);
 
     // Finally, run any custom checkers.

>From b46e232bddb4d1e7818d7601a62127d6c82325d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 17:21:52 +0100
Subject: [PATCH 07/11] [NFC][analyzer] Make ExprEngine::getBuilderContext()
 const

Not related to the rest of the changes, just a drive-by minor
improvement.
---
 .../clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h       | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index d37f33da71ab9..4ab7863b4cb26 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -215,8 +215,9 @@ class ExprEngine {
   getCrossTranslationUnitContext() {
     return &CTU;
   }
+  
 
-  const NodeBuilderContext &getBuilderContext() {
+  const NodeBuilderContext &getBuilderContext() const {
     assert(currBldrCtx);
     return *currBldrCtx;
   }

>From 2f0e40b5bd7740c58666fdd7dcf6d0c4d9632f49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 18:12:05 +0100
Subject: [PATCH 08/11] [NFC][analyzer] Introduce
 setCurrLocationContextAndBlock

---
 .../Core/PathSensitive/ExprEngine.h           | 34 ++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 4ab7863b4cb26..155f41c53d679 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -159,7 +159,28 @@ class ExprEngine {
   SValBuilder &svalBuilder;
 
   unsigned int currStmtIdx = 0;
+
+  /// Pointer to a so-called NodeBuilderContext object which has three
+  /// independent roles:
+  /// - It holds a pointer to the CFGBlock that is currently under analysis.
+  ///   (This is the primary way to get the current block.)
+  /// - It holds a pointer to the current LocationContext. (This is rarely used
+  ///   and may be 'stale', the location context is usually queried from an
+  ///   ExplodedNode or a ProgramPoint.)
+  /// - It can be used for constructing `NodeBuilder`s. Practically all
+  ///   `NodeBuilder` objects are useless complications in the code, so I
+  ///   intend to replace them with direct use of `ExprEngine::makeNode`.
+  /// TODO: Eventually `currBldrCtx` should be replaced by two separate fields:
+  /// `const CFGBlock *CurrBlock` & `const LocationContext *CurrLocationContext`
+  /// that are kept up-to-date and are almost always non-null during the
+  /// analysis. I will switch to this more natural representation when
+  /// `NodeBuilder`s are eliminated from the code.
   const NodeBuilderContext *currBldrCtx = nullptr;
+  /// Historically `currBldrCtx` pointed to a local variable in some stack
+  /// frame. This field is introduced as a temporary measure to allow a gradual
+  /// transition. Do not reference this outside of setLocationContextAndBlock!
+  /// TODO: Remove this temporary hack.
+  std::optional<NodeBuilderContext> OwnedCurrBldrCtx;
 
   /// Helper object to determine if an Objective-C message expression
   /// implicitly never returns.
@@ -215,7 +236,18 @@ class ExprEngine {
   getCrossTranslationUnitContext() {
     return &CTU;
   }
-  
+
+  // FIXME: Ideally the body of this method should look like
+  //   CurrLocationContext = LC;
+  //   CurrBlock = B;
+  // where CurrLocationContext and CurrBlock are new member variables that
+  // fulfill the roles of `currBldrCtx` in a more natural way.
+  // This implementation is a temporary measure to allow a gradual transition.
+  void setCurrLocationContextAndBlock(const LocationContext *LC,
+                                      const CFGBlock *B) {
+    OwnedCurrBldrCtx.emplace(Engine, B, LC);
+    currBldrCtx = &*OwnedCurrBldrCtx;
+  }
 
   const NodeBuilderContext &getBuilderContext() const {
     assert(currBldrCtx);

>From 0dfc00a9573a743a6ce72d2890a263146466f14e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 18:26:02 +0100
Subject: [PATCH 09/11] [NFC][analyzer] Prefer currBldrCtx instead of local
 aliases

In functions that save one of their parameters in `currBldrCtx`, prefer
using `currBldrCtx` instead of the local alias.

This prepares the ground for setting `currBldrCtx` _outside of the
function_ in a more central location and removing the NodeBuilderContext
parameter from the signature of these functions.
---
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 1c5b968734e89..184a733bb8bdb 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -2844,7 +2844,7 @@ void ExprEngine::processBranch(
 
   // Check for NULL conditions; e.g. "for(;;)"
   if (!Condition) {
-    BranchNodeBuilder NullCondBldr(Dst, BldCtx, DstT, DstF);
+    BranchNodeBuilder NullCondBldr(Dst, *currBldrCtx, DstT, DstF);
     NullCondBldr.generateNode(Pred->getState(), true, Pred);
     return;
   }
@@ -2852,7 +2852,7 @@ void ExprEngine::processBranch(
   if (const auto *Ex = dyn_cast<Expr>(Condition))
     Condition = Ex->IgnoreParens();
 
-  Condition = ResolveCondition(Condition, BldCtx.getBlock());
+  Condition = ResolveCondition(Condition, currBldrCtx->getBlock());
   PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
                                 Condition->getBeginLoc(),
                                 "Error evaluating branch");
@@ -2864,7 +2864,7 @@ void ExprEngine::processBranch(
   if (CheckersOutSet.empty())
     return;
 
-  BranchNodeBuilder Builder(Dst, BldCtx, DstT, DstF);
+  BranchNodeBuilder Builder(Dst, *currBldrCtx, DstT, DstF);
   for (ExplodedNode *PredN : CheckersOutSet) {
     ProgramStateRef PrevState = PredN->getState();
 
@@ -2970,7 +2970,7 @@ void ExprEngine::processStaticInitializer(
   const auto *VD = cast<VarDecl>(DS->getSingleDecl());
   ProgramStateRef state = Pred->getState();
   bool initHasRun = state->contains<InitializedGlobalsSet>(VD);
-  BranchNodeBuilder Builder(Dst, BuilderCtx, DstT, DstF);
+  BranchNodeBuilder Builder(Dst, *currBldrCtx, DstT, DstF);
 
   if (!initHasRun) {
     state = state->add<InitializedGlobalsSet>(VD);
@@ -3100,7 +3100,7 @@ void ExprEngine::processSwitch(NodeBuilderContext &BC, const SwitchStmt *Switch,
   currBldrCtx = &BC;
   const Expr *Condition = Switch->getCond();
 
-  SwitchNodeBuilder Builder(Dst, BC);
+  SwitchNodeBuilder Builder(Dst, *currBldrCtx);
   ExplodedNodeSet CheckersOutSet;
 
   getCheckerManager().runCheckersForBranchCondition(

>From 157edc3504752ca4530174529dd274902f6b713e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 18:52:49 +0100
Subject: [PATCH 10/11] [NFC][analyzer] Eliminate local NodeBuilderContexts
 part 1

Previously `ExprEngine::currBldrCtx` pointed to locally initialized
`NodeBuilderContext` instances. This commit replaces some of these with
calls to `ExprEngine::setCurrLocationContextAndBlock()``.

This simplifies the signature of some complex methods and prepares the
ground for the eventual removal of `NodeBuilderContext`.
---
 .../Core/PathSensitive/ExprEngine.h           | 17 +++++++---------
 clang/lib/StaticAnalyzer/Core/CoreEngine.cpp  | 14 ++++++-------
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp  | 20 +++++++++----------
 3 files changed, 23 insertions(+), 28 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 155f41c53d679..5d0f332288109 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -378,9 +378,9 @@ class ExprEngine {
   /// processing the 'effects' of a branch condition. If the branch condition
   /// is a loop condition, IterationsCompletedInLoop is the number of completed
   /// iterations (otherwise it's std::nullopt).
-  void processBranch(const Stmt *Condition, NodeBuilderContext &BuilderCtx,
-                     ExplodedNode *Pred, ExplodedNodeSet &Dst,
-                     const CFGBlock *DstT, const CFGBlock *DstF,
+  void processBranch(const Stmt *Condition, ExplodedNode *Pred,
+                     ExplodedNodeSet &Dst, const CFGBlock *DstT,
+                     const CFGBlock *DstF,
                      std::optional<unsigned> IterationsCompletedInLoop);
 
   /// Called by CoreEngine.
@@ -394,11 +394,8 @@ class ExprEngine {
 
   /// Called by CoreEngine.  Used to processing branching behavior
   /// at static initializers.
-  void processStaticInitializer(const DeclStmt *DS,
-                                NodeBuilderContext& BuilderCtx,
-                                ExplodedNode *Pred,
-                                ExplodedNodeSet &Dst,
-                                const CFGBlock *DstT,
+  void processStaticInitializer(const DeclStmt *DS, ExplodedNode *Pred,
+                                ExplodedNodeSet &Dst, const CFGBlock *DstT,
                                 const CFGBlock *DstF);
 
   /// processIndirectGoto - Called by CoreEngine.  Used to generate successor
@@ -408,8 +405,8 @@ class ExprEngine {
 
   /// ProcessSwitch - Called by CoreEngine.  Used to generate successor
   ///  nodes by processing the 'effects' of a switch statement.
-  void processSwitch(NodeBuilderContext &BC, const SwitchStmt *Switch,
-                     ExplodedNode *Pred, ExplodedNodeSet &Dst);
+  void processSwitch(const SwitchStmt *Switch, ExplodedNode *Pred,
+                     ExplodedNodeSet &Dst);
 
   /// Called by CoreEngine.  Used to notify checkers that processing a
   /// function has begun. Called for both inlined and top-level functions.
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index d32db1669e1fe..f3c8beb8e1fa8 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -452,9 +452,9 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) {
         return;
 
       case Stmt::SwitchStmtClass: {
-        NodeBuilderContext Ctx(*this, B, Pred);
+        ExprEng.setCurrLocationContextAndBlock(Pred->getLocationContext(), B);
         ExplodedNodeSet Dst;
-        ExprEng.processSwitch(Ctx, cast<SwitchStmt>(Term), Pred, Dst);
+        ExprEng.processSwitch(cast<SwitchStmt>(Term), Pred, Dst);
         // Enqueue the new frontier onto the worklist.
         enqueue(Dst);
         return;
@@ -492,9 +492,9 @@ void CoreEngine::HandleCallEnter(const CallEnter &CE, ExplodedNode *Pred) {
 void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term,
                                 const CFGBlock * B, ExplodedNode *Pred) {
   assert(B->succ_size() == 2);
-  NodeBuilderContext Ctx(*this, B, Pred);
+  ExprEng.setCurrLocationContextAndBlock(Pred->getLocationContext(), B);
   ExplodedNodeSet Dst;
-  ExprEng.processBranch(Cond, Ctx, Pred, Dst, *(B->succ_begin()),
+  ExprEng.processBranch(Cond, Pred, Dst, *(B->succ_begin()),
                         *(B->succ_begin() + 1),
                         getCompletedIterationCount(B, Pred));
   // Enqueue the new frontier onto the worklist.
@@ -516,10 +516,10 @@ void CoreEngine::HandleCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE,
 void CoreEngine::HandleStaticInit(const DeclStmt *DS, const CFGBlock *B,
                                   ExplodedNode *Pred) {
   assert(B->succ_size() == 2);
-  NodeBuilderContext Ctx(*this, B, Pred);
+  ExprEng.setCurrLocationContextAndBlock(Pred->getLocationContext(), B);
   ExplodedNodeSet Dst;
-  ExprEng.processStaticInitializer(DS, Ctx, Pred, Dst,
-                                  *(B->succ_begin()), *(B->succ_begin()+1));
+  ExprEng.processStaticInitializer(DS, Pred, Dst, *(B->succ_begin()),
+                                   *(B->succ_begin() + 1));
   // Enqueue the new frontier onto the worklist.
   enqueue(Dst);
 }
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 184a733bb8bdb..3c96cfabb3f70 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -2835,12 +2835,11 @@ assumeCondition(const Stmt *Condition, ExplodedNode *N) {
 }
 
 void ExprEngine::processBranch(
-    const Stmt *Condition, NodeBuilderContext &BldCtx, ExplodedNode *Pred,
-    ExplodedNodeSet &Dst, const CFGBlock *DstT, const CFGBlock *DstF,
+    const Stmt *Condition, ExplodedNode *Pred, ExplodedNodeSet &Dst,
+    const CFGBlock *DstT, const CFGBlock *DstF,
     std::optional<unsigned> IterationsCompletedInLoop) {
   assert((!Condition || !isa<CXXBindTemporaryExpr>(Condition)) &&
          "CXXBindTemporaryExprs are handled by processBindTemporary.");
-  currBldrCtx = &BldCtx;
 
   // Check for NULL conditions; e.g. "for(;;)"
   if (!Condition) {
@@ -2962,11 +2961,11 @@ void ExprEngine::processBranch(
 REGISTER_TRAIT_WITH_PROGRAMSTATE(InitializedGlobalsSet,
                                  llvm::ImmutableSet<const VarDecl *>)
 
-void ExprEngine::processStaticInitializer(
-    const DeclStmt *DS, NodeBuilderContext &BuilderCtx, ExplodedNode *Pred,
-    ExplodedNodeSet &Dst, const CFGBlock *DstT, const CFGBlock *DstF) {
-  currBldrCtx = &BuilderCtx;
-
+void ExprEngine::processStaticInitializer(const DeclStmt *DS,
+                                          ExplodedNode *Pred,
+                                          ExplodedNodeSet &Dst,
+                                          const CFGBlock *DstT,
+                                          const CFGBlock *DstF) {
   const auto *VD = cast<VarDecl>(DS->getSingleDecl());
   ProgramStateRef state = Pred->getState();
   bool initHasRun = state->contains<InitializedGlobalsSet>(VD);
@@ -3095,9 +3094,8 @@ void ExprEngine::processEndOfFunction(NodeBuilderContext& BC,
 
 /// ProcessSwitch - Called by CoreEngine.  Used to generate successor
 ///  nodes by processing the 'effects' of a switch statement.
-void ExprEngine::processSwitch(NodeBuilderContext &BC, const SwitchStmt *Switch,
-                               ExplodedNode *Pred, ExplodedNodeSet &Dst) {
-  currBldrCtx = &BC;
+void ExprEngine::processSwitch(const SwitchStmt *Switch, ExplodedNode *Pred,
+                               ExplodedNodeSet &Dst) {
   const Expr *Condition = Switch->getCond();
 
   SwitchNodeBuilder Builder(Dst, *currBldrCtx);

>From 116ca3848d41253f001575644737b559b7d8a8fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 6 Mar 2026 20:42:03 +0100
Subject: [PATCH 11/11] [NFC][analyzer] Unify setCurrLocationContextAndBlock in
 HandleBlockExit

The "bulk" of `CoreEngine::HandleBlockExit` is the switch within
`if const Stmt *Term = B->(getTerminatorStmt())`. In this big block
every `case` used equivalent `NodeBuilderContext` instances, so this
commit unifies their initialization to a single call to
`setCurrLocationContextAndBlock()`.

In the more complex branches (e.g. `HandleBranch`) the
`NodeBuilderContext` was initialized via via calls to
`setCurrLocationContextAndBlock` (which also makes it available as the
`currBldrCtx`); while the simple branches previously just had their
`NodeBuilderContext` in a local variable.

Note that at the very end of `HandleBlockExit` there is a path where it
calls `HandleVirtualBaseBranch`, which calls `HandelBlockEdge` and sets
up its `NodeBuilderContext` in a different way (there "the" block is the
_destination_ block, not the exited block). (That logic is not changed.)

Also, fix a const correctness issue in the constructor of
`IndirectGotoNodeBuilder` (which was introduced by one of my earlier
commits).
---
 .../Core/PathSensitive/CoreEngine.h               |  5 +++--
 .../Core/PathSensitive/ExprEngine.h               |  1 -
 clang/lib/StaticAnalyzer/Core/CoreEngine.cpp      | 15 ++++++---------
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp      |  3 +--
 4 files changed, 10 insertions(+), 14 deletions(-)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
index 21749b0f42b2b..6b70fda42819c 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
@@ -339,8 +339,9 @@ class IndirectGotoNodeBuilder : public NodeBuilder {
   const Expr *Target;
 
 public:
-  IndirectGotoNodeBuilder(ExplodedNodeSet &DstSet, NodeBuilderContext &Ctx,
-                          const Expr *Tgt, const CFGBlock *Dispatch)
+  IndirectGotoNodeBuilder(ExplodedNodeSet &DstSet,
+                          const NodeBuilderContext &Ctx, const Expr *Tgt,
+                          const CFGBlock *Dispatch)
       : NodeBuilder(DstSet, Ctx), DispatchBlock(*Dispatch), Target(Tgt) {}
 
   using iterator = CFGBlock::const_succ_iterator;
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 5d0f332288109..45459e79546b0 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -387,7 +387,6 @@ class ExprEngine {
   /// Used to generate successor nodes for temporary destructors depending
   /// on whether the corresponding constructor was visited.
   void processCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE,
-                                     NodeBuilderContext &BldCtx,
                                      ExplodedNode *Pred, ExplodedNodeSet &Dst,
                                      const CFGBlock *DstT,
                                      const CFGBlock *DstF);
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index f3c8beb8e1fa8..cf39008387bae 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -357,6 +357,8 @@ void CoreEngine::HandleBlockEntrance(const BlockEntrance &L,
 
 void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) {
   if (const Stmt *Term = B->getTerminatorStmt()) {
+    ExprEng.setCurrLocationContextAndBlock(Pred->getLocationContext(), B);
+
     switch (Term->getStmtClass()) {
       default:
         llvm_unreachable("Analysis for this terminator not implemented.");
@@ -425,11 +427,10 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) {
       case Stmt::IndirectGotoStmtClass: {
         // Only 1 successor: the indirect goto dispatch block.
         assert(B->succ_size() == 1);
-        NodeBuilderContext Ctx(*this, B, Pred);
         ExplodedNodeSet Dst;
         IndirectGotoNodeBuilder Builder(
-            Dst, Ctx, cast<IndirectGotoStmt>(Term)->getTarget(),
-            *(B->succ_begin()));
+            Dst, ExprEng.getBuilderContext(),
+            cast<IndirectGotoStmt>(Term)->getTarget(), *(B->succ_begin()));
 
         ExprEng.processIndirectGoto(Builder, Pred);
         // Enqueue the new frontier onto the worklist.
@@ -452,7 +453,6 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) {
         return;
 
       case Stmt::SwitchStmtClass: {
-        ExprEng.setCurrLocationContextAndBlock(Pred->getLocationContext(), B);
         ExplodedNodeSet Dst;
         ExprEng.processSwitch(cast<SwitchStmt>(Term), Pred, Dst);
         // Enqueue the new frontier onto the worklist.
@@ -492,7 +492,6 @@ void CoreEngine::HandleCallEnter(const CallEnter &CE, ExplodedNode *Pred) {
 void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term,
                                 const CFGBlock * B, ExplodedNode *Pred) {
   assert(B->succ_size() == 2);
-  ExprEng.setCurrLocationContextAndBlock(Pred->getLocationContext(), B);
   ExplodedNodeSet Dst;
   ExprEng.processBranch(Cond, Pred, Dst, *(B->succ_begin()),
                         *(B->succ_begin() + 1),
@@ -505,10 +504,9 @@ void CoreEngine::HandleCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE,
                                               const CFGBlock *B,
                                               ExplodedNode *Pred) {
   assert(B->succ_size() == 2);
-  NodeBuilderContext Ctx(*this, B, Pred);
   ExplodedNodeSet Dst;
-  ExprEng.processCleanupTemporaryBranch(BTE, Ctx, Pred, Dst, *(B->succ_begin()),
-                                       *(B->succ_begin() + 1));
+  ExprEng.processCleanupTemporaryBranch(BTE, Pred, Dst, *(B->succ_begin()),
+                                        *(B->succ_begin() + 1));
   // Enqueue the new frontier onto the worklist.
   enqueue(Dst);
 }
@@ -516,7 +514,6 @@ void CoreEngine::HandleCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE,
 void CoreEngine::HandleStaticInit(const DeclStmt *DS, const CFGBlock *B,
                                   ExplodedNode *Pred) {
   assert(B->succ_size() == 2);
-  ExprEng.setCurrLocationContextAndBlock(Pred->getLocationContext(), B);
   ExplodedNodeSet Dst;
   ExprEng.processStaticInitializer(DS, Pred, Dst, *(B->succ_begin()),
                                    *(B->succ_begin() + 1));
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 3c96cfabb3f70..121f8913f9389 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1638,12 +1638,11 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
 }
 
 void ExprEngine::processCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE,
-                                               NodeBuilderContext &BldCtx,
                                                ExplodedNode *Pred,
                                                ExplodedNodeSet &Dst,
                                                const CFGBlock *DstT,
                                                const CFGBlock *DstF) {
-  BranchNodeBuilder TempDtorBuilder(Dst, BldCtx, DstT, DstF);
+  BranchNodeBuilder TempDtorBuilder(Dst, *currBldrCtx, DstT, DstF);
   ProgramStateRef State = Pred->getState();
   const LocationContext *LC = Pred->getLocationContext();
   if (getObjectUnderConstruction(State, BTE, LC)) {



More information about the cfe-commits mailing list