https://github.com/NagyDonat created https://github.com/llvm/llvm-project/pull/178678
This commit refactors `ExprEngine::processSwitch()` and related logic to make it easier to understand and "prepare the ground" for planned functional changes. Unfortunately there were many idiosyncratic decisions in this part of the engine -- e.g. `BranchNodeBuilder` does not derive from `NodeBuilder` and doesn't use a `NodeBuilderContext`. For now I left these skeletons in the closet, but I tried to pick the low-hanging fruit and moved `processSwitch` a bit closer to its "big sibling" `processBranch`. For example I moved the initialization of the node builder into the body of `processSwitch` because if I want to trigger `BranchCondition` callbacks from this method (the way `processBranch` does it) I will need to iterate over the nodes created by checkers and construct a new node builder in each iteration. (By the way this question is a bit academical because AFAIK there are no BranchCondition callbacks that actually split the state -- and until recently `processBranch` would've badly mishandled such callbacks -- but properly handling state splits is much easier than documenting and checking that `BranchCondition` callbacks are not allowed to split the state.) From 27798c4fe5cfccb8cea95bb84af6fbc505a3509f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <[email protected]> Date: Thu, 29 Jan 2026 16:07:45 +0100 Subject: [PATCH] [NFC][analyzer] Refactor switch handling in the engine This commit refactors `ExprEngine::processSwitch()` and related logic to make it easier to understand and "prepare the ground" for planned functional changes. Unfortunately there were many idiosyncratic decisions in this part of the engine -- e.g. `BranchNodeBuilder` does not derive from `NodeBuilder` and doesn't use a `NodeBuilderContext`. For now I left these skeletons in the closet, but I tried to pick the low-hanging fruit and moved `processSwitch` a bit closer to its "big sibling" `processBranch`. For example I moved the initialization of the node builder into the body of `processSwitch` because if I want to trigger `BranchCondition` callbacks from this method (the way `processBranch` does it) I will need to iterate over the nodes created by checkers and construct a new node builder in each iteration. (By the way this question is a bit academical because AFAIK there are no BranchCondition callbacks that actually split the state -- and until recently `processBranch` would've badly mishandled such callbacks -- but properly handling state splits is much easier than documenting and checking that `BranchCondition` callbacks are not allowed to split the state.) --- .../Core/PathSensitive/CoreEngine.h | 49 +++--------- .../Core/PathSensitive/ExprEngine.h | 3 +- clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 15 ++-- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 77 ++++++++----------- 4 files changed, 50 insertions(+), 94 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h index b2b4e8729af25..8e179e8a58ae6 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h @@ -514,57 +514,26 @@ class IndirectGotoNodeBuilder { }; class SwitchNodeBuilder { - CoreEngine& Eng; + const CoreEngine &Eng; const CFGBlock *Src; const Expr *Condition; ExplodedNode *Pred; public: - SwitchNodeBuilder(ExplodedNode *pred, const CFGBlock *src, - const Expr *condition, CoreEngine* eng) - : Eng(*eng), Src(src), Condition(condition), Pred(pred) {} - - class iterator { - friend class SwitchNodeBuilder; + SwitchNodeBuilder(ExplodedNode *P, const CFGBlock *S, const Expr *C, + CoreEngine &E) + : Eng(E), Src(S), Condition(C), Pred(P) {} - CFGBlock::const_succ_reverse_iterator I; + using iterator = CFGBlock::const_succ_reverse_iterator; - iterator(CFGBlock::const_succ_reverse_iterator i) : I(i) {} + iterator begin() { return Src->succ_rbegin() + 1; } + iterator end() { return Src->succ_rend(); } - public: - iterator &operator++() { ++I; return *this; } - bool operator!=(const iterator &X) const { return I != X.I; } - bool operator==(const iterator &X) const { return I == X.I; } - - const CaseStmt *getCase() const { - return cast<CaseStmt>((*I)->getLabel()); - } - - const CFGBlock *getBlock() const { - return *I; - } - }; - - iterator begin() { return iterator(Src->succ_rbegin()+1); } - iterator end() { return iterator(Src->succ_rend()); } - - const SwitchStmt *getSwitch() const { - return cast<SwitchStmt>(Src->getTerminator()); - } - - ExplodedNode *generateCaseStmtNode(const iterator &I, + ExplodedNode *generateCaseStmtNode(const CFGBlock *Block, ProgramStateRef State); ExplodedNode *generateDefaultCaseNode(ProgramStateRef State, - bool isSink = false); - - const Expr *getCondition() const { return Condition; } - - ProgramStateRef getState() const { return Pred->State; } - - const LocationContext *getLocationContext() const { - return Pred->getLocationContext(); - } + bool IsSink = false); }; } // namespace ento diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index d184986cda15d..e5980fa2e2a4d 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -361,7 +361,8 @@ class ExprEngine { /// ProcessSwitch - Called by CoreEngine. Used to generate successor /// nodes by processing the 'effects' of a switch statement. - void processSwitch(SwitchNodeBuilder& builder); + void processSwitch(const SwitchStmt *Switch, CoreEngine &CoreEng, + const CFGBlock *B, ExplodedNode *Pred); /// 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 95a843ee87571..857b2f0689e05 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -449,10 +449,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { return; case Stmt::SwitchStmtClass: { - SwitchNodeBuilder builder(Pred, B, cast<SwitchStmt>(Term)->getCond(), - this); - - ExprEng.processSwitch(builder); + ExprEng.processSwitch(cast<SwitchStmt>(Term), *this, B, Pred); return; } @@ -748,13 +745,11 @@ IndirectGotoNodeBuilder::generateNode(const iterator &I, return Succ; } -ExplodedNode* -SwitchNodeBuilder::generateCaseStmtNode(const iterator &I, - ProgramStateRef St) { +ExplodedNode *SwitchNodeBuilder::generateCaseStmtNode(const CFGBlock *Block, + ProgramStateRef St) { bool IsNew; - ExplodedNode *Succ = - Eng.G.getNode(BlockEdge(Src, I.getBlock(), Pred->getLocationContext()), - St, false, &IsNew); + ExplodedNode *Succ = Eng.G.getNode( + BlockEdge(Src, Block, Pred->getLocationContext()), St, false, &IsNew); Succ->addPredecessor(Pred, Eng.G); if (!IsNew) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index a6a96b594fe85..98e4d79841491 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3107,37 +3107,34 @@ 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(SwitchNodeBuilder& builder) { - using iterator = SwitchNodeBuilder::iterator; +void ExprEngine::processSwitch(const SwitchStmt *Switch, CoreEngine &CoreEng, + const CFGBlock *B, ExplodedNode *Pred) { + const Expr *Condition = Switch->getCond(); - ProgramStateRef state = builder.getState(); - const Expr *CondE = builder.getCondition(); - SVal CondV_untested = state->getSVal(CondE, builder.getLocationContext()); + SwitchNodeBuilder Builder(Pred, B, Condition, CoreEng); - if (CondV_untested.isUndef()) { - //ExplodedNode* N = builder.generateDefaultCaseNode(state, true); - // FIXME: add checker - //UndefBranches.insert(N); + ProgramStateRef State = Pred->getState(); + SVal CondV = State->getSVal(Condition, Pred->getLocationContext()); + if (CondV.isUndef()) { + // ExplodedNode* N = builder.generateDefaultCaseNode(state, true); + // FIXME: add checker + // UndefBranches.insert(N); return; } - DefinedOrUnknownSVal CondV = CondV_untested.castAs<DefinedOrUnknownSVal>(); - - ProgramStateRef DefaultSt = state; - iterator I = builder.begin(), EI = builder.end(); - bool defaultIsFeasible = I == EI; + std::optional<NonLoc> CondNL = CondV.getAs<NonLoc>(); - for ( ; I != EI; ++I) { + for (const CFGBlock *Block : Builder) { // Successor may be pruned out during CFG construction. - if (!I.getBlock()) + if (!Block) continue; - const CaseStmt *Case = I.getCase(); + const CaseStmt *Case = cast<CaseStmt>(Block->getLabel()); // Evaluate the LHS of the case value. llvm::APSInt V1 = Case->getLHS()->EvaluateKnownConstInt(getContext()); - assert(V1.getBitWidth() == getContext().getIntWidth(CondE->getType())); + assert(V1.getBitWidth() == getContext().getIntWidth(Condition->getType())); // Get the RHS of the case, if it exists. llvm::APSInt V2; @@ -3146,29 +3143,25 @@ void ExprEngine::processSwitch(SwitchNodeBuilder& builder) { else V2 = V1; - ProgramStateRef StateCase; - if (std::optional<NonLoc> NL = CondV.getAs<NonLoc>()) - std::tie(StateCase, DefaultSt) = - DefaultSt->assumeInclusiveRange(*NL, V1, V2); - else // UnknownVal - StateCase = DefaultSt; - - if (StateCase) - builder.generateCaseStmtNode(I, StateCase); - - // Now "assume" that the case doesn't match. Add this state - // to the default state (if it is feasible). - if (DefaultSt) - defaultIsFeasible = true; - else { - defaultIsFeasible = false; - break; + ProgramStateRef StateMatching; + if (CondNL) { + // Split the state: this "case:" matches / does not match. + std::tie(StateMatching, State) = + State->assumeInclusiveRange(*CondNL, V1, V2); + } else { + // The switch condition is UnknownVal, so we enter each "case:" without + // any state update. + StateMatching = State; } - } - if (!defaultIsFeasible) - return; + if (StateMatching) + Builder.generateCaseStmtNode(Block, StateMatching); + // If _not_ entering the current case is infeasible, we are done with + // processing this branch. + if (!State) + return; + } // If we have switch(enum value), the default branch is not // feasible if all of the enum constants not covered by 'case:' statements // are not feasible values for the switch condition. @@ -3176,14 +3169,12 @@ void ExprEngine::processSwitch(SwitchNodeBuilder& builder) { // Note that this isn't as accurate as it could be. Even if there isn't // a case for a particular enum value as long as that enum value isn't // feasible then it shouldn't be considered for making 'default:' reachable. - const SwitchStmt *SS = builder.getSwitch(); - const Expr *CondExpr = SS->getCond()->IgnoreParenImpCasts(); - if (CondExpr->getType()->isEnumeralType()) { - if (SS->isAllEnumCasesCovered()) + if (Condition->IgnoreParenImpCasts()->getType()->isEnumeralType()) { + if (Switch->isAllEnumCasesCovered()) return; } - builder.generateDefaultCaseNode(DefaultSt); + Builder.generateDefaultCaseNode(State); } //===----------------------------------------------------------------------===// _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
