[clang] [NFC][analyzer] Remove NodeBuilders around defaultEvalCall (PR #203923)
DonĂ¡t Nagy via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 15 08:23:48 PDT 2026
https://github.com/NagyDonat created https://github.com/llvm/llvm-project/pull/203923
This change removes `NodeBuilder`s from the functions connected to `defaultEvalCall` that were previously passing around `NodeBuilder` arguments instead of the more usual `ExplodedNodeSet &Dst` out-parameters.
Although these `NodeBuilder`s "travelled through" many functions, their usage pattern was relatively simple and their back-and-forth set manipulation didn't provide any advantage over a plain exploded node set.
In addition to the removal of the `NodeBuilder`s, this commit performs minor simplifications in the affected code and renames the old method `BifurcateCall` to the more specific `dynDispatchBifurcate` (because the old name was too vague now that we also have `ctuBifurcate`).
------
Note for reviewers: **I strongly suggest reviewing commit by commit** because the individual commits clearly show the "sweep" through the call graph that gradually extracts and then removes the `takeNodes` operations.
Before creating this PR, I made the following call graph of the affected functions; this may be useful during the review:
<img width="1881" height="971" alt="call_eval_call_graph" src="https://github.com/user-attachments/assets/e32f94b6-8322-4d91-8914-7a18a258ca2e" />
>From 77fc48d9972d8ce9ab820ccef1078a29506b4e23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Thu, 11 Jun 2026 14:31:09 +0200
Subject: [PATCH 01/13] Remove NodeBuilder from ExprEngine::inlineCall
This method took a NodeBuilder as an argument, unconditionally called
`Bldr.takeNodes(Pred)` on it and did nothing else with it.
This commit removes the `NodeBuilder` argument and temporarily places
the `Bldr.takeNodes(Pred)` calls just before the call sites of
`inlineCall`. Follow-up commits will consolidate (in fact, eliminate)
these repeated calls.
---
.../Core/PathSensitive/ExprEngine.h | 2 +-
.../Core/ExprEngineCallAndReturn.cpp | 17 ++++++++---------
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index bb2de45cec92a..5667375d37bb3 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -908,7 +908,7 @@ class ExprEngine {
const StackFrame *SF);
void inlineCall(WorkList *WList, const CallEvent &Call, const Decl *D,
- NodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef State);
+ ExplodedNode *Pred, ProgramStateRef State);
void ctuBifurcate(const CallEvent &Call, const Decl *D, NodeBuilder &Bldr,
ExplodedNode *Pred, ProgramStateRef State);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 4cbcaa2721639..50e82c8d4b220 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -502,13 +502,15 @@ void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
(IK == CTUPhase1InliningKind::Small &&
isSmall(AMgr.getAnalysisDeclContext(D)));
if (DoInline) {
- inlineCall(Engine.getWorkList(), Call, D, Bldr, Pred, State);
+ Bldr.takeNodes(Pred);
+ inlineCall(Engine.getWorkList(), Call, D, Pred, State);
return;
}
const bool BState = State->get<CTUDispatchBifurcation>();
if (!BState) { // This is the first time we see this foreign function.
// Enqueue it to be analyzed in the second (ctu) phase.
- inlineCall(Engine.getCTUWorkList(), Call, D, Bldr, Pred, State);
+ Bldr.takeNodes(Pred);
+ inlineCall(Engine.getCTUWorkList(), Call, D, Pred, State);
// Conservatively evaluate in the first phase.
ConservativeEvalState = State->set<CTUDispatchBifurcation>(true);
conservativeEvalCall(Call, Bldr, Pred, ConservativeEvalState);
@@ -517,12 +519,13 @@ void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
}
return;
}
- inlineCall(Engine.getWorkList(), Call, D, Bldr, Pred, State);
+ Bldr.takeNodes(Pred);
+ inlineCall(Engine.getWorkList(), Call, D, Pred, State);
}
void ExprEngine::inlineCall(WorkList *WList, const CallEvent &Call,
- const Decl *D, NodeBuilder &Bldr,
- ExplodedNode *Pred, ProgramStateRef State) {
+ const Decl *D, ExplodedNode *Pred,
+ ProgramStateRef State) {
assert(D);
const StackFrame *CallerSF = Pred->getStackFrame();
@@ -556,10 +559,6 @@ void ExprEngine::inlineCall(WorkList *WList, const CallEvent &Call,
WList->enqueue(N);
}
- // If we decided to inline the call, the successor has been manually
- // added onto the work list so remove it from the node builder.
- Bldr.takeNodes(Pred);
-
NumInlinedCalls++;
Engine.FunctionSummaries->bumpNumTimesInlined(D);
>From 930c1bfb2e3ec0097409b398fe27f5b04f615fc9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Thu, 11 Jun 2026 15:56:17 +0200
Subject: [PATCH 02/13] Remove NodeBuilder from conservativeEvalCall
Previously `ExprEngine::conservativeEvalCall` used `generateNode` to
make a single node and place it in the `Frontier` (destination set) of
the `NodeBuilder`.
The method `NodeBuilder::generateNode` removes the predecessor (`Pred`)
from the `Frontier` of the `NodeBuilder` instance, makes a new node and
then adds it to the `Frontier`, which is equivalent to a chain of
`takeNodes(Pred)` then `makeNode` then `addNodes`.
This commit decomposes the `generateNode` call into these steps and
moves the `NodeBuilder` manipulation out of `conservativeEvalCall`,
which will now just make a node (after creating the right `State`) and
return it.
Follow-up commits will consolidate and eliminate the `takeNodes` calls
which now appear separately on practically every each execution path.
(And the `addNodes` call will be replaced with `ExplodedNodeSet::insert`
when the node builder is eliminated.)
---
.../Core/PathSensitive/ExprEngine.h | 4 ++--
.../Core/ExprEngineCallAndReturn.cpp | 24 ++++++++++++-------
2 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 5667375d37bb3..ae29f35a950b3 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -918,8 +918,8 @@ class ExprEngine {
/// Conservatively evaluate call by invalidating regions and binding
/// a conjured return value.
- void conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr,
- ExplodedNode *Pred, ProgramStateRef State);
+ ExplodedNode *conservativeEvalCall(const CallEvent &Call, ExplodedNode *Pred,
+ ProgramStateRef State);
/// Either inline or process the call conservatively (or both), based
/// on DynamicDispatchBifurcation data.
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 50e82c8d4b220..2d5caa3b189f6 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -513,9 +513,10 @@ void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
inlineCall(Engine.getCTUWorkList(), Call, D, Pred, State);
// Conservatively evaluate in the first phase.
ConservativeEvalState = State->set<CTUDispatchBifurcation>(true);
- conservativeEvalCall(Call, Bldr, Pred, ConservativeEvalState);
+ Bldr.addNodes(conservativeEvalCall(Call, Pred, ConservativeEvalState));
} else {
- conservativeEvalCall(Call, Bldr, Pred, State);
+ Bldr.takeNodes(Pred);
+ Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
}
return;
}
@@ -836,14 +837,15 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call,
// Conservatively evaluate call by invalidating regions and binding
// a conjured return value.
-void ExprEngine::conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr,
- ExplodedNode *Pred, ProgramStateRef State) {
+ExplodedNode *ExprEngine::conservativeEvalCall(const CallEvent &Call,
+ ExplodedNode *Pred,
+ ProgramStateRef State) {
State = Call.invalidateRegions(getNumVisitedCurrent(), State);
State = bindReturnValue(Call, Pred->getStackFrame(), State);
// And make the result node.
static SimpleProgramPointTag PT("ExprEngine", "Conservative eval call");
- Bldr.generateNode(Call.getProgramPoint(false, &PT), State, Pred);
+ return Engine.makeNode(Call.getProgramPoint(false, &PT), State, Pred);
}
ExprEngine::CallInlinePolicy
@@ -1252,7 +1254,8 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
// Don't inline if we're not in any dynamic dispatch mode.
if (Options.getIPAMode() != IPAK_DynamicDispatch) {
- conservativeEvalCall(Call, Bldr, Pred, State);
+ Bldr.takeNodes(Pred);
+ Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
return;
}
}
@@ -1267,7 +1270,8 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
State, dyn_cast_or_null<CXXConstructExpr>(E), Call.getStackFrame());
// Also handle the return value and invalidate the regions.
- conservativeEvalCall(Call, Bldr, Pred, State);
+ Bldr.takeNodes(Pred);
+ Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
}
void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
@@ -1288,7 +1292,8 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
// If inline failed, or we are on the path where we assume we
// don't have enough info about the receiver to inline, conjure the
// return value and invalidate the regions.
- conservativeEvalCall(Call, Bldr, Pred, State);
+ Bldr.takeNodes(Pred);
+ Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
return;
}
@@ -1302,7 +1307,8 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
ProgramStateRef NoIState =
State->set<DynamicDispatchBifurcationMap>(BifurReg,
DynamicDispatchModeConservative);
- conservativeEvalCall(Call, Bldr, Pred, NoIState);
+ Bldr.takeNodes(Pred);
+ Bldr.addNodes(conservativeEvalCall(Call, Pred, NoIState));
NumOfDynamicDispatchPathSplits++;
}
>From 6263ca71fbfbe616ab770843ca489a75e25502b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 12 Jun 2026 16:15:06 +0200
Subject: [PATCH 03/13] Rename variable Dst to DstEval in performTrivialCopy
...because I want to replace the `NodeBuilder` (which currently collects
the new nodes) with a customary out-parameter `ExplodedNodeSet &Dst` in
a follow-up change, and this local occupies that name.
---
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index 291533d0c3289..3f259a3b5b769 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -67,7 +67,7 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
const StackFrame *SF = Pred->getStackFrame();
const Expr *CallExpr = Call.getOriginExpr();
- ExplodedNodeSet Dst;
+ ExplodedNodeSet DstEval;
Bldr.takeNodes(Pred);
assert(ThisRD);
@@ -87,17 +87,17 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
evalLocation(Tmp, CallExpr, VExpr, Pred, Pred->getState(), V,
/*isLoad=*/true);
for (ExplodedNode *N : Tmp)
- evalBind(Dst, CallExpr, N, ThisVal, V, !AlwaysReturnsLValue);
+ evalBind(DstEval, CallExpr, N, ThisVal, V, !AlwaysReturnsLValue);
} else {
// We can't copy empty classes because of empty base class optimization.
// In that case, copying the empty base class subobject would overwrite the
// object that it overlaps with - so let's not do that.
// See issue-157467.cpp for an example.
- Dst.insert(Pred);
+ DstEval.insert(Pred);
}
PostStmt PS(CallExpr, SF);
- for (ExplodedNode *N : Dst) {
+ for (ExplodedNode *N : DstEval) {
ProgramStateRef State = N->getState();
if (AlwaysReturnsLValue)
State = State->BindExpr(CallExpr, SF, ThisVal);
>From 9ca2b560f6bba60bd59ff6360c8f29cab63e5541 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 12 Jun 2026 16:21:02 +0200
Subject: [PATCH 04/13] Extract Bldr.takeNodes(Pred) from performTrivialCopy
`performTrivialCopy` unconditionally calls `Bldr.takeNodes(Pred)` so
this commit moves it out of the function.
In `handleConstructor` this "cancels out" the side effect of the
constructor of the node builder `Bldr`, which inserted all elements of
`DstPreCall` into `DstEvaluated`. (Removing `DstPreCall` from the
argument list of the constructor of `Bldr` means skipping this initial
insertion -- which is equivalent to inserting them and taking out each
node in the subsequent for loop.)
In `defaultEvalCall` this commit just places `Bldr.takeNodes(Pred)`
before the call to `performTrivialCopy`. As this also appears on many
alternative execution paths, follow-up commits will be able to
consolidate its uses.
---
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 3 +--
clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp | 1 +
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index 3f259a3b5b769..e1d5869d95e21 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -68,7 +68,6 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
const Expr *CallExpr = Call.getOriginExpr();
ExplodedNodeSet DstEval;
- Bldr.takeNodes(Pred);
assert(ThisRD);
@@ -729,7 +728,7 @@ void ExprEngine::handleConstructor(const Expr *E,
if (CE && CE->getConstructor()->isTrivial() &&
CE->getConstructor()->isCopyOrMoveConstructor() &&
!CallOpts.IsArrayCtorOrDtor) {
- NodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx);
+ NodeBuilder Bldr(DstEvaluated, *currBldrCtx);
// FIXME: Handle other kinds of trivial constructors as well.
for (ExplodedNode *N : DstPreCall)
performTrivialCopy(Bldr, N, *Call);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 2d5caa3b189f6..a111db8653f07 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -1228,6 +1228,7 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
// Special-case trivial assignment operators.
if (isTrivialObjectAssignment(Call)) {
+ Bldr.takeNodes(Pred);
performTrivialCopy(Bldr, Pred, Call);
return;
}
>From 1b5c08d558b2f14676f8ce1bf10c86d27f3540f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 12 Jun 2026 17:04:19 +0200
Subject: [PATCH 05/13] Extract Bldr.takeNodes(Pred) from ctuBifurcate
It was called on all paths within `ctuBifurcate`, place it before each
call to `ctuBifurcate` instead. These will be moved further by the
follow-up commits.
---
.../StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index a111db8653f07..159e642f91c83 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -502,25 +502,21 @@ void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
(IK == CTUPhase1InliningKind::Small &&
isSmall(AMgr.getAnalysisDeclContext(D)));
if (DoInline) {
- Bldr.takeNodes(Pred);
inlineCall(Engine.getWorkList(), Call, D, Pred, State);
return;
}
const bool BState = State->get<CTUDispatchBifurcation>();
if (!BState) { // This is the first time we see this foreign function.
// Enqueue it to be analyzed in the second (ctu) phase.
- Bldr.takeNodes(Pred);
inlineCall(Engine.getCTUWorkList(), Call, D, Pred, State);
// Conservatively evaluate in the first phase.
ConservativeEvalState = State->set<CTUDispatchBifurcation>(true);
Bldr.addNodes(conservativeEvalCall(Call, Pred, ConservativeEvalState));
} else {
- Bldr.takeNodes(Pred);
Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
}
return;
}
- Bldr.takeNodes(Pred);
inlineCall(Engine.getWorkList(), Call, D, Pred, State);
}
@@ -1260,6 +1256,7 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
return;
}
}
+ Bldr.takeNodes(Pred);
ctuBifurcate(Call, D, Bldr, Pred, State);
return;
}
@@ -1288,8 +1285,10 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
State->get<DynamicDispatchBifurcationMap>(BifurReg);
if (BState) {
// If we are on "inline path", keep inlining if possible.
- if (*BState == DynamicDispatchModeInlined)
+ if (*BState == DynamicDispatchModeInlined) {
+ Bldr.takeNodes(Pred);
ctuBifurcate(Call, D, Bldr, Pred, State);
+ }
// If inline failed, or we are on the path where we assume we
// don't have enough info about the receiver to inline, conjure the
// return value and invalidate the regions.
@@ -1303,6 +1302,7 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
ProgramStateRef IState =
State->set<DynamicDispatchBifurcationMap>(BifurReg,
DynamicDispatchModeInlined);
+ Bldr.takeNodes(Pred);
ctuBifurcate(Call, D, Bldr, Pred, IState);
ProgramStateRef NoIState =
>From fbbfeb16f6878a591fc5650d21eeb81605bbb0a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 12 Jun 2026 17:07:48 +0200
Subject: [PATCH 06/13] Extract Bldr.takeNodes(Pred) from BifurcateCall
It was called on all paths within `BifurcateCall`, place it before each
call to `BifurcateCall` instead. These will be moved further by the
follow-up commits.
---
clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 159e642f91c83..640b7ce52c6ef 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -1245,6 +1245,7 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
// Explore with and without inlining the call.
if (Options.getIPAMode() == IPAK_DynamicDispatchBifurcate) {
+ Bldr.takeNodes(Pred);
BifurcateCall(RD.getDispatchRegion(), Call, D, Bldr, Pred);
return;
}
@@ -1285,14 +1286,11 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
State->get<DynamicDispatchBifurcationMap>(BifurReg);
if (BState) {
// If we are on "inline path", keep inlining if possible.
- if (*BState == DynamicDispatchModeInlined) {
- Bldr.takeNodes(Pred);
+ if (*BState == DynamicDispatchModeInlined)
ctuBifurcate(Call, D, Bldr, Pred, State);
- }
// If inline failed, or we are on the path where we assume we
// don't have enough info about the receiver to inline, conjure the
// return value and invalidate the regions.
- Bldr.takeNodes(Pred);
Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
return;
}
@@ -1302,13 +1300,11 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
ProgramStateRef IState =
State->set<DynamicDispatchBifurcationMap>(BifurReg,
DynamicDispatchModeInlined);
- Bldr.takeNodes(Pred);
ctuBifurcate(Call, D, Bldr, Pred, IState);
ProgramStateRef NoIState =
State->set<DynamicDispatchBifurcationMap>(BifurReg,
DynamicDispatchModeConservative);
- Bldr.takeNodes(Pred);
Bldr.addNodes(conservativeEvalCall(Call, Pred, NoIState));
NumOfDynamicDispatchPathSplits++;
>From f91e2a5a074f97cefe6e67f3cf08a0311d923052 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Fri, 12 Jun 2026 17:12:35 +0200
Subject: [PATCH 07/13] Extract Bldr.takeNodes(Pred) from performTrivialCopy
`defaultEvalCall` called `Bldr.takeNodes(Pred)` on every execution path,
so this commit moves it out of the function.
In most call sites the node builder was constructed with the 3-argument
constructor that inserts all nodes from the first argument into the
second argument (which is the `Frontier`, the out-parameter of the node
builer) -- which was followed by a for loop that took out each of these
nodes from the `Frontier` with the `takeNodes` calls in
`defaultEvalCall`. This commit "cancels out" these back-and-forth set
manipulations.
In `VisitObjCMessage` the analogous loop is more complex, and will be
refactored in a separate commit; this change just temporarily places
`Bldr.takeNodes(Pred)` in front of the `defaultEvalCall` invocation.
---
clang/lib/StaticAnalyzer/Core/CheckerManager.cpp | 2 +-
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 6 +++---
clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp | 5 -----
clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp | 1 +
4 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
index 80c03899d1e39..1a57a40c02441 100644
--- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
@@ -809,7 +809,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst,
// If none of the checkers evaluated the call, ask ExprEngine to handle it.
if (!evaluatorChecker) {
- NodeBuilder B(Pred, Dst, Eng.getBuilderContext());
+ NodeBuilder B(Dst, Eng.getBuilderContext());
Eng.defaultEvalCall(B, Pred, *UpdatedCall, CallOpts);
}
}
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index e1d5869d95e21..a202d92b8b74c 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -861,7 +861,7 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType,
*Call, *this);
ExplodedNodeSet DstInvalidated;
- NodeBuilder Bldr(DstPreCall, DstInvalidated, *currBldrCtx);
+ NodeBuilder Bldr(DstInvalidated, *currBldrCtx);
for (ExplodedNode *N : DstPreCall)
defaultEvalCall(Bldr, N, *Call, CallOpts);
@@ -886,7 +886,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE,
*Call, *this);
ExplodedNodeSet DstPostCall;
- NodeBuilder CallBldr(DstPreCall, DstPostCall, *currBldrCtx);
+ NodeBuilder CallBldr(DstPostCall, *currBldrCtx);
for (ExplodedNode *I : DstPreCall) {
// Operator new calls (CXXNewExpr) are intentionally not eval-called,
// because it does not make sense to eval-call user-provided functions.
@@ -1094,7 +1094,7 @@ void ExprEngine::VisitCXXDeleteExpr(const CXXDeleteExpr *CDE,
ExplodedNodeSet DstPostCall;
if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
- NodeBuilder Bldr(DstPreCall, DstPostCall, *currBldrCtx);
+ NodeBuilder Bldr(DstPostCall, *currBldrCtx);
for (ExplodedNode *I : DstPreCall) {
// Intentionally either inline or conservative eval-call the operator
// delete, but avoid triggering an eval-call event for checkers.
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 640b7ce52c6ef..47f67f17b17ba 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -1224,7 +1224,6 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
// Special-case trivial assignment operators.
if (isTrivialObjectAssignment(Call)) {
- Bldr.takeNodes(Pred);
performTrivialCopy(Bldr, Pred, Call);
return;
}
@@ -1245,19 +1244,16 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
// Explore with and without inlining the call.
if (Options.getIPAMode() == IPAK_DynamicDispatchBifurcate) {
- Bldr.takeNodes(Pred);
BifurcateCall(RD.getDispatchRegion(), Call, D, Bldr, Pred);
return;
}
// Don't inline if we're not in any dynamic dispatch mode.
if (Options.getIPAMode() != IPAK_DynamicDispatch) {
- Bldr.takeNodes(Pred);
Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
return;
}
}
- Bldr.takeNodes(Pred);
ctuBifurcate(Call, D, Bldr, Pred, State);
return;
}
@@ -1269,7 +1265,6 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
State, dyn_cast_or_null<CXXConstructExpr>(E), Call.getStackFrame());
// Also handle the return value and invalidate the regions.
- Bldr.takeNodes(Pred);
Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
}
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index 15f0275691e92..15b970fc673d5 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -285,6 +285,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
}
}
+ Bldr.takeNodes(Pred);
defaultEvalCall(Bldr, Pred, *UpdatedMsg);
}
>From 3ef458c83ec290bea6e73a62ebf94206ae032136 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Wed, 10 Jun 2026 14:51:02 +0200
Subject: [PATCH 08/13] Simplify an old-style loop
---
clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index 15b970fc673d5..e20e9e3ec5c92 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -258,9 +258,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
ExplodedNodeSet dstEval;
NodeBuilder Bldr(dstGenericPrevisit, dstEval, *currBldrCtx);
- for (ExplodedNodeSet::iterator DI = dstGenericPrevisit.begin(),
- DE = dstGenericPrevisit.end(); DI != DE; ++DI) {
- ExplodedNode *Pred = *DI;
+ for (ExplodedNode *Pred : dstGenericPrevisit) {
ProgramStateRef State = Pred->getState();
CallEventRef<ObjCMethodCall> UpdatedMsg = Msg.cloneWithState(State);
>From 1d2ccbaf5c018dbeac52842baf74948936a5bc77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Mon, 15 Jun 2026 13:52:07 +0200
Subject: [PATCH 09/13] Simplify last builder in VisitObjCMessage
The constructor of this node builder inserted the exploded node set
`dstGenericPrevisit` into `dstEval` but then each of these nodes was
removed from the set by either one of the two `generateSink` calls or by
the call `Bldr.takeNodes(Pred)`.
As this temporary presence of the nodes in the set doesn't affect
anything, this commit:
- removes this first argument of the `NodeBuilder` constructor;
- removes `Bldr.takeNodes(Pred)`;
- replaces the `generateSink` calls with `makePostStmtNode` calls where
`MarkAsSink` is true.
Note that a `generatNode` call has three effects: removing the old node
from the `Frontier`, making the new node and inserting the new node into
the `Frontier` -- but the third one is irrelevant for sinks because
`ExplodedNodeSet`s ignore the insertion of sink nodes. Therefore
"cancelling out" the removal of the old node leaves just the node-making
call (`makePostStmtNode` in this case).
---
clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index e20e9e3ec5c92..d5c38890800db 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -256,7 +256,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
// Proceed with evaluate the message expression.
ExplodedNodeSet dstEval;
- NodeBuilder Bldr(dstGenericPrevisit, dstEval, *currBldrCtx);
+ NodeBuilder Bldr(dstEval, *currBldrCtx);
for (ExplodedNode *Pred : dstGenericPrevisit) {
ProgramStateRef State = Pred->getState();
@@ -268,7 +268,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
if (ObjCNoRet.isImplicitNoReturn(ME)) {
// If we raise an exception, for now treat it as a sink.
// Eventually we will want to handle exceptions properly.
- Bldr.generateSink(ME, Pred, State);
+ Engine.makePostStmtNode(ME, State, Pred, /*MarkAsSink=*/true);
continue;
}
}
@@ -278,12 +278,11 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
if (ObjCNoRet.isImplicitNoReturn(ME)) {
// If we raise an exception, for now treat it as a sink.
// Eventually we will want to handle exceptions properly.
- Bldr.generateSink(ME, Pred, Pred->getState());
+ Engine.makePostStmtNode(ME, State, Pred, /*MarkAsSink=*/true);
continue;
}
}
- Bldr.takeNodes(Pred);
defaultEvalCall(Bldr, Pred, *UpdatedMsg);
}
>From d3cfb6d1ab41490ba330fb044a7ad873e3eabfde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Mon, 15 Jun 2026 16:34:57 +0200
Subject: [PATCH 10/13] Remove NodeBuilders from the defaultEvalCall function
family
Instead of passing around the NodeBuilder, use its destination (a/k/a
`Frontier`) set directly. Previous changes reduced the usage of these
node builders to a few `addNodes` calls, which directly correspond to
`ExplodedNodeSet::insert`.
---
.../Core/PathSensitive/ExprEngine.h | 11 +++----
.../StaticAnalyzer/Core/CheckerManager.cpp | 6 ++--
.../lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 17 ++++------
.../Core/ExprEngineCallAndReturn.cpp | 32 +++++++++----------
.../StaticAnalyzer/Core/ExprEngineObjC.cpp | 3 +-
5 files changed, 30 insertions(+), 39 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index ae29f35a950b3..5f19784158ed2 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -772,7 +772,7 @@ class ExprEngine {
const CallEvent &Call);
/// Default implementation of call evaluation.
- void defaultEvalCall(NodeBuilder &B, ExplodedNode *Pred,
+ void defaultEvalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
const CallEvent &Call,
const EvalCallOptions &CallOpts = {});
@@ -910,7 +910,7 @@ class ExprEngine {
void inlineCall(WorkList *WList, const CallEvent &Call, const Decl *D,
ExplodedNode *Pred, ProgramStateRef State);
- void ctuBifurcate(const CallEvent &Call, const Decl *D, NodeBuilder &Bldr,
+ void ctuBifurcate(const CallEvent &Call, const Decl *D, ExplodedNodeSet &Dst,
ExplodedNode *Pred, ProgramStateRef State);
/// Returns true if the CTU analysis is running its second phase.
@@ -923,15 +923,14 @@ class ExprEngine {
/// Either inline or process the call conservatively (or both), based
/// on DynamicDispatchBifurcation data.
- void BifurcateCall(const MemRegion *BifurReg,
- const CallEvent &Call, const Decl *D, NodeBuilder &Bldr,
- ExplodedNode *Pred);
+ void BifurcateCall(const MemRegion *BifurReg, const CallEvent &Call,
+ const Decl *D, ExplodedNodeSet &Dst, ExplodedNode *Pred);
bool replayWithoutInlining(ExplodedNode *P, const StackFrame *CalleeSF);
/// Models a trivial copy or move constructor or trivial assignment operator
/// call with a simple bind.
- void performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
+ void performTrivialCopy(ExplodedNodeSet &Dst, ExplodedNode *Pred,
const CallEvent &Call);
/// If the value of the given expression \p InitWithAdjustments is a NonLoc,
diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
index 1a57a40c02441..4db6b6ecaa9f7 100644
--- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
@@ -808,10 +808,8 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst,
}
// If none of the checkers evaluated the call, ask ExprEngine to handle it.
- if (!evaluatorChecker) {
- NodeBuilder B(Dst, Eng.getBuilderContext());
- Eng.defaultEvalCall(B, Pred, *UpdatedCall, CallOpts);
- }
+ if (!evaluatorChecker)
+ Eng.defaultEvalCall(Dst, Pred, *UpdatedCall, CallOpts);
}
}
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index a202d92b8b74c..b4873a2280ff0 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -44,7 +44,7 @@ void ExprEngine::CreateCXXTemporaryObject(const MaterializeTemporaryExpr *ME,
// FIXME: This is the sort of code that should eventually live in a Core
// checker rather than as a special case in ExprEngine.
-void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
+void ExprEngine::performTrivialCopy(ExplodedNodeSet &Dst, ExplodedNode *Pred,
const CallEvent &Call) {
SVal ThisVal;
bool AlwaysReturnsLValue;
@@ -95,14 +95,13 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
DstEval.insert(Pred);
}
- PostStmt PS(CallExpr, SF);
for (ExplodedNode *N : DstEval) {
ProgramStateRef State = N->getState();
if (AlwaysReturnsLValue)
State = State->BindExpr(CallExpr, SF, ThisVal);
else
State = bindReturnValue(Call, SF, State);
- Bldr.generateNode(PS, State, N);
+ Dst.insert(Engine.makePostStmtNode(CallExpr, State, Pred));
}
}
@@ -728,10 +727,9 @@ void ExprEngine::handleConstructor(const Expr *E,
if (CE && CE->getConstructor()->isTrivial() &&
CE->getConstructor()->isCopyOrMoveConstructor() &&
!CallOpts.IsArrayCtorOrDtor) {
- NodeBuilder Bldr(DstEvaluated, *currBldrCtx);
// FIXME: Handle other kinds of trivial constructors as well.
for (ExplodedNode *N : DstPreCall)
- performTrivialCopy(Bldr, N, *Call);
+ performTrivialCopy(DstEvaluated, N, *Call);
} else {
for (ExplodedNode *N : DstPreCall)
@@ -861,9 +859,8 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType,
*Call, *this);
ExplodedNodeSet DstInvalidated;
- NodeBuilder Bldr(DstInvalidated, *currBldrCtx);
for (ExplodedNode *N : DstPreCall)
- defaultEvalCall(Bldr, N, *Call, CallOpts);
+ defaultEvalCall(DstInvalidated, N, *Call, CallOpts);
getCheckerManager().runCheckersForPostCall(Dst, DstInvalidated,
*Call, *this);
@@ -886,7 +883,6 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE,
*Call, *this);
ExplodedNodeSet DstPostCall;
- NodeBuilder CallBldr(DstPostCall, *currBldrCtx);
for (ExplodedNode *I : DstPreCall) {
// Operator new calls (CXXNewExpr) are intentionally not eval-called,
// because it does not make sense to eval-call user-provided functions.
@@ -896,7 +892,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE,
// is what we want anyway.
// So the best is to not allow eval-calling CXXNewExprs from checkers.
// Checkers can provide their pre/post-call callbacks if needed.
- defaultEvalCall(CallBldr, I, *Call);
+ defaultEvalCall(DstPostCall, I, *Call);
}
// If the call is inlined, DstPostCall will be empty and we bail out now.
@@ -1094,13 +1090,12 @@ void ExprEngine::VisitCXXDeleteExpr(const CXXDeleteExpr *CDE,
ExplodedNodeSet DstPostCall;
if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
- NodeBuilder Bldr(DstPostCall, *currBldrCtx);
for (ExplodedNode *I : DstPreCall) {
// Intentionally either inline or conservative eval-call the operator
// delete, but avoid triggering an eval-call event for checkers.
// As detailed at handling CXXNewExprs, in short, because it does not
// really make sense to eval-call user-provided functions.
- defaultEvalCall(Bldr, I, *Call);
+ defaultEvalCall(DstPostCall, I, *Call);
}
} else {
DstPostCall = std::move(DstPreCall);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 47f67f17b17ba..099db8d081dfe 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -493,7 +493,7 @@ REGISTER_MAP_WITH_PROGRAMSTATE(DynamicDispatchBifurcationMap,
REGISTER_TRAIT_WITH_PROGRAMSTATE(CTUDispatchBifurcation, bool)
void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
- NodeBuilder &Bldr, ExplodedNode *Pred,
+ ExplodedNodeSet &Dst, ExplodedNode *Pred,
ProgramStateRef State) {
ProgramStateRef ConservativeEvalState = nullptr;
if (Call.isForeign() && !isSecondPhaseCTU()) {
@@ -511,9 +511,9 @@ void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
inlineCall(Engine.getCTUWorkList(), Call, D, Pred, State);
// Conservatively evaluate in the first phase.
ConservativeEvalState = State->set<CTUDispatchBifurcation>(true);
- Bldr.addNodes(conservativeEvalCall(Call, Pred, ConservativeEvalState));
+ Dst.insert(conservativeEvalCall(Call, Pred, ConservativeEvalState));
} else {
- Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
+ Dst.insert(conservativeEvalCall(Call, Pred, State));
}
return;
}
@@ -1216,7 +1216,7 @@ static bool isTrivialObjectAssignment(const CallEvent &Call) {
return MD->isTrivial();
}
-void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
+void ExprEngine::defaultEvalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
const CallEvent &Call,
const EvalCallOptions &CallOpts) {
// Make sure we have the most recent state attached to the call.
@@ -1224,7 +1224,7 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
// Special-case trivial assignment operators.
if (isTrivialObjectAssignment(Call)) {
- performTrivialCopy(Bldr, Pred, Call);
+ performTrivialCopy(Dst, Pred, Call);
return;
}
@@ -1244,17 +1244,17 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
// Explore with and without inlining the call.
if (Options.getIPAMode() == IPAK_DynamicDispatchBifurcate) {
- BifurcateCall(RD.getDispatchRegion(), Call, D, Bldr, Pred);
+ BifurcateCall(RD.getDispatchRegion(), Call, D, Dst, Pred);
return;
}
// Don't inline if we're not in any dynamic dispatch mode.
if (Options.getIPAMode() != IPAK_DynamicDispatch) {
- Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
+ Dst.insert(conservativeEvalCall(Call, Pred, State));
return;
}
}
- ctuBifurcate(Call, D, Bldr, Pred, State);
+ ctuBifurcate(Call, D, Dst, Pred, State);
return;
}
}
@@ -1265,12 +1265,12 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
State, dyn_cast_or_null<CXXConstructExpr>(E), Call.getStackFrame());
// Also handle the return value and invalidate the regions.
- Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
+ Dst.insert(conservativeEvalCall(Call, Pred, State));
}
-void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
- const CallEvent &Call, const Decl *D,
- NodeBuilder &Bldr, ExplodedNode *Pred) {
+void ExprEngine::BifurcateCall(const MemRegion *BifurReg, const CallEvent &Call,
+ const Decl *D, ExplodedNodeSet &Dst,
+ ExplodedNode *Pred) {
assert(BifurReg);
BifurReg = BifurReg->StripCasts();
@@ -1282,11 +1282,11 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
if (BState) {
// If we are on "inline path", keep inlining if possible.
if (*BState == DynamicDispatchModeInlined)
- ctuBifurcate(Call, D, Bldr, Pred, State);
+ ctuBifurcate(Call, D, Dst, Pred, State);
// If inline failed, or we are on the path where we assume we
// don't have enough info about the receiver to inline, conjure the
// return value and invalidate the regions.
- Bldr.addNodes(conservativeEvalCall(Call, Pred, State));
+ Dst.insert(conservativeEvalCall(Call, Pred, State));
return;
}
@@ -1295,12 +1295,12 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg,
ProgramStateRef IState =
State->set<DynamicDispatchBifurcationMap>(BifurReg,
DynamicDispatchModeInlined);
- ctuBifurcate(Call, D, Bldr, Pred, IState);
+ ctuBifurcate(Call, D, Dst, Pred, IState);
ProgramStateRef NoIState =
State->set<DynamicDispatchBifurcationMap>(BifurReg,
DynamicDispatchModeConservative);
- Bldr.addNodes(conservativeEvalCall(Call, Pred, NoIState));
+ Dst.insert(conservativeEvalCall(Call, Pred, NoIState));
NumOfDynamicDispatchPathSplits++;
}
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index d5c38890800db..d4be050427b8b 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -256,7 +256,6 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
// Proceed with evaluate the message expression.
ExplodedNodeSet dstEval;
- NodeBuilder Bldr(dstEval, *currBldrCtx);
for (ExplodedNode *Pred : dstGenericPrevisit) {
ProgramStateRef State = Pred->getState();
@@ -283,7 +282,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
}
}
- defaultEvalCall(Bldr, Pred, *UpdatedMsg);
+ defaultEvalCall(dstEval, Pred, *UpdatedMsg);
}
// If there were constructors called for object-type arguments, clean them up.
>From d3ee7461bd1671bddb99586215ea333aa9aa47f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Mon, 15 Jun 2026 16:46:16 +0200
Subject: [PATCH 11/13] [side] Simplify isImplicitNoReturn logic in
VisitObjCMessage
Unify two `if` blocks that had identical bodies and related conditions.
---
.../StaticAnalyzer/Core/ExprEngineObjC.cpp | 26 +++++--------------
1 file changed, 7 insertions(+), 19 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index d4be050427b8b..26b8ca3e1f2bc 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -261,25 +261,13 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
ProgramStateRef State = Pred->getState();
CallEventRef<ObjCMethodCall> UpdatedMsg = Msg.cloneWithState(State);
- if (UpdatedMsg->isInstanceMessage()) {
- SVal recVal = UpdatedMsg->getReceiverSVal();
- if (!recVal.isUndef()) {
- if (ObjCNoRet.isImplicitNoReturn(ME)) {
- // If we raise an exception, for now treat it as a sink.
- // Eventually we will want to handle exceptions properly.
- Engine.makePostStmtNode(ME, State, Pred, /*MarkAsSink=*/true);
- continue;
- }
- }
- } else {
- // Check for special class methods that are known to not return
- // and that we should treat as a sink.
- if (ObjCNoRet.isImplicitNoReturn(ME)) {
- // If we raise an exception, for now treat it as a sink.
- // Eventually we will want to handle exceptions properly.
- Engine.makePostStmtNode(ME, State, Pred, /*MarkAsSink=*/true);
- continue;
- }
+ if (ObjCNoRet.isImplicitNoReturn(ME) &&
+ !(UpdatedMsg->isInstanceMessage() &&
+ UpdatedMsg->getReceiverSVal().isUndef())) {
+ // If we raise an exception, for now treat it as a sink.
+ // Eventually we will want to handle exceptions properly.
+ Engine.makePostStmtNode(ME, State, Pred, /*MarkAsSink=*/true);
+ continue;
}
defaultEvalCall(dstEval, Pred, *UpdatedMsg);
>From ad27ff870f3978ffa98915130bf9ce5c3f0e8bc5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Mon, 15 Jun 2026 16:52:51 +0200
Subject: [PATCH 12/13] [side] Simplify ctuBifurcate
Avoid duplicating the call of `conservativeEvalCall`.
---
clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 099db8d081dfe..33eba2e69e58e 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -495,7 +495,6 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(CTUDispatchBifurcation, bool)
void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
ExplodedNodeSet &Dst, ExplodedNode *Pred,
ProgramStateRef State) {
- ProgramStateRef ConservativeEvalState = nullptr;
if (Call.isForeign() && !isSecondPhaseCTU()) {
const auto IK = AMgr.options.getCTUPhase1Inlining();
const bool DoInline = IK == CTUPhase1InliningKind::All ||
@@ -510,11 +509,9 @@ void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D,
// Enqueue it to be analyzed in the second (ctu) phase.
inlineCall(Engine.getCTUWorkList(), Call, D, Pred, State);
// Conservatively evaluate in the first phase.
- ConservativeEvalState = State->set<CTUDispatchBifurcation>(true);
- Dst.insert(conservativeEvalCall(Call, Pred, ConservativeEvalState));
- } else {
- Dst.insert(conservativeEvalCall(Call, Pred, State));
+ State = State->set<CTUDispatchBifurcation>(true);
}
+ Dst.insert(conservativeEvalCall(Call, Pred, State));
return;
}
inlineCall(Engine.getWorkList(), Call, D, Pred, State);
>From 2fbe1642d585fd876419782c13a855e6cc34f631 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.nagy at ericsson.com>
Date: Mon, 15 Jun 2026 16:57:39 +0200
Subject: [PATCH 13/13] [side] Rename BifurcateCall to dynDispatchBifurcate
As I'm already changing the parametrization of this method, I take this
opportunity to update its name to clarify its role and conform to the
naming conventions.
This is an old method and when it was originally introduced,
'BifuracetCall' was a reasonable name for it, but since then we also
have 'ctuBifurcate', so it is better to use a more specific name for
this method.
---
.../clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h | 5 +++--
.../lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp | 9 +++++----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 5f19784158ed2..ce9b20185444e 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -923,8 +923,9 @@ class ExprEngine {
/// Either inline or process the call conservatively (or both), based
/// on DynamicDispatchBifurcation data.
- void BifurcateCall(const MemRegion *BifurReg, const CallEvent &Call,
- const Decl *D, ExplodedNodeSet &Dst, ExplodedNode *Pred);
+ void dynDispatchBifurcate(const MemRegion *BifurReg, const CallEvent &Call,
+ const Decl *D, ExplodedNodeSet &Dst,
+ ExplodedNode *Pred);
bool replayWithoutInlining(ExplodedNode *P, const StackFrame *CalleeSF);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 33eba2e69e58e..d5733d40ff60d 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -1241,7 +1241,7 @@ void ExprEngine::defaultEvalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
// Explore with and without inlining the call.
if (Options.getIPAMode() == IPAK_DynamicDispatchBifurcate) {
- BifurcateCall(RD.getDispatchRegion(), Call, D, Dst, Pred);
+ dynDispatchBifurcate(RD.getDispatchRegion(), Call, D, Dst, Pred);
return;
}
@@ -1265,9 +1265,10 @@ void ExprEngine::defaultEvalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
Dst.insert(conservativeEvalCall(Call, Pred, State));
}
-void ExprEngine::BifurcateCall(const MemRegion *BifurReg, const CallEvent &Call,
- const Decl *D, ExplodedNodeSet &Dst,
- ExplodedNode *Pred) {
+void ExprEngine::dynDispatchBifurcate(const MemRegion *BifurReg,
+ const CallEvent &Call, const Decl *D,
+ ExplodedNodeSet &Dst,
+ ExplodedNode *Pred) {
assert(BifurReg);
BifurReg = BifurReg->StripCasts();
More information about the cfe-commits
mailing list