https://github.com/NagyDonat updated https://github.com/llvm/llvm-project/pull/180960
From f309d2aef493fe4443fbb768d828bdc1da87a93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <[email protected]> Date: Wed, 11 Feb 2026 15:45:30 +0100 Subject: [PATCH 1/2] [NFCI][analyzer] Regularize NodeBuilder classes Previously `IndirectGotoNodeBuilder` and `SwitchNodeBuilder` were not subclasses of the base class `NodeBuilder` and duplicated some `generateNode()`-like functions. This commit reimplements these two classes as subclasses of `NodeBuilder`. Updating `SwitchNodeBuilder` is a prerequisite for activating the `BranchCondition` checkers on the condition of the `switch` statement -- because `CheckerContext` requires the presence of a `NodeBuilder`. Updating `IndirectGotoNodeBuilder` doesn't have any analogous goals -- I'm just doing it for the sake of consistency. I also added some very basic tests because this area wasn't properly covered by the old tests. --- .../Core/PathSensitive/CoreEngine.h | 51 ++++---- .../Core/PathSensitive/ExprEngine.h | 7 +- clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 81 ++++++------- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 46 +++----- clang/test/Analysis/indirect-goto-basics.c | 83 +++++++++++++ clang/test/Analysis/switch-basics.c | 110 ++++++++++++++++++ 6 files changed, 276 insertions(+), 102 deletions(-) create mode 100644 clang/test/Analysis/indirect-goto-basics.c create mode 100644 clang/test/Analysis/switch-basics.c diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h index 96ddd12b286a6..6d7dca2622a89 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h @@ -399,55 +399,56 @@ class BranchNodeBuilder: public NodeBuilder { ExplodedNode *Pred); }; -class IndirectGotoNodeBuilder { - const CoreEngine &Eng; - const CFGBlock *Src; +class IndirectGotoNodeBuilder : public NodeBuilder { const CFGBlock &DispatchBlock; - const Expr *E; - ExplodedNode *Pred; + const Expr *Target; public: - IndirectGotoNodeBuilder(ExplodedNode *Pred, const CFGBlock *Src, - const Expr *E, const CFGBlock *Dispatch, - const CoreEngine *Eng) - : Eng(*Eng), Src(Src), DispatchBlock(*Dispatch), E(E), Pred(Pred) {} + IndirectGotoNodeBuilder(ExplodedNode *SrcNode, ExplodedNodeSet &DstSet, + NodeBuilderContext &Ctx, const Expr *Tgt, + const CFGBlock *Dispatch) + : NodeBuilder(SrcNode, DstSet, Ctx), DispatchBlock(*Dispatch), + Target(Tgt) { + // The indirect goto node builder does not generate autotransitions. + takeNodes(SrcNode); + } using iterator = CFGBlock::const_succ_iterator; iterator begin() { return DispatchBlock.succ_begin(); } iterator end() { return DispatchBlock.succ_end(); } - ExplodedNode *generateNode(const CFGBlock *Block, ProgramStateRef State, - bool IsSink = false); + using NodeBuilder::generateNode; - const Expr *getTarget() const { return E; } + ExplodedNode *generateNode(const CFGBlock *Block, ProgramStateRef State, + ExplodedNode *Pred); - ProgramStateRef getState() const { return Pred->State; } + const Expr *getTarget() const { return Target; } const LocationContext *getLocationContext() const { - return Pred->getLocationContext(); + return C.getLocationContext(); } }; -class SwitchNodeBuilder { - const CoreEngine &Eng; - const CFGBlock *Src; - ExplodedNode *Pred; - +class SwitchNodeBuilder : public NodeBuilder { public: - SwitchNodeBuilder(ExplodedNode *P, const CFGBlock *S, CoreEngine &E) - : Eng(E), Src(S), Pred(P) {} + SwitchNodeBuilder(ExplodedNode *SrcNode, ExplodedNodeSet &DstSet, + const NodeBuilderContext &Ctx) + : NodeBuilder(SrcNode, DstSet, Ctx) { + // The switch node builder does not generate autotransitions. + takeNodes(SrcNode); + } using iterator = CFGBlock::const_succ_reverse_iterator; - iterator begin() { return Src->succ_rbegin() + 1; } - iterator end() { return Src->succ_rend(); } + iterator begin() { return C.getBlock()->succ_rbegin() + 1; } + iterator end() { return C.getBlock()->succ_rend(); } ExplodedNode *generateCaseStmtNode(const CFGBlock *Block, - ProgramStateRef State); + ProgramStateRef State, ExplodedNode *Pred); ExplodedNode *generateDefaultCaseNode(ProgramStateRef State, - bool IsSink = false); + ExplodedNode *Pred); }; } // namespace ento diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index 6705716932bbc..20f62f93f9095 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -355,12 +355,13 @@ class ExprEngine { /// processIndirectGoto - Called by CoreEngine. Used to generate successor /// nodes by processing the 'effects' of a computed goto jump. - void processIndirectGoto(IndirectGotoNodeBuilder& builder); + void processIndirectGoto(IndirectGotoNodeBuilder &Builder, + ExplodedNode *Pred); /// ProcessSwitch - Called by CoreEngine. Used to generate successor /// nodes by processing the 'effects' of a switch statement. - void processSwitch(const SwitchStmt *Switch, CoreEngine &CoreEng, - const CFGBlock *B, ExplodedNode *Pred); + void processSwitch(NodeBuilderContext &BC, 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 bfaa874f1632a..8f087cc53aaa9 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -425,12 +425,23 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { case Stmt::IndirectGotoStmtClass: { // Only 1 successor: the indirect goto dispatch block. assert(B->succ_size() == 1); - - IndirectGotoNodeBuilder - builder(Pred, B, cast<IndirectGotoStmt>(Term)->getTarget(), - *(B->succ_begin()), this); - - ExprEng.processIndirectGoto(builder); + NodeBuilderContext Ctx(*this, B, Pred); + ExplodedNodeSet Dst; + IndirectGotoNodeBuilder Builder( + Pred, Dst, Ctx, cast<IndirectGotoStmt>(Term)->getTarget(), + *(B->succ_begin())); + + ExprEng.processIndirectGoto(Builder, Pred); + // Enqueue the new frontier onto the worklist. + llvm::errs() << "Pred location is "; + Pred->getLocation().dump(); + llvm::errs() << "\n"; + for (auto *N : Dst) { + llvm::errs() << "Enqueueing node at "; + N->getLocation().dump(); + llvm::errs() << "\n"; + WList->enqueue(N); + } return; } @@ -449,7 +460,12 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { return; case Stmt::SwitchStmtClass: { - ExprEng.processSwitch(cast<SwitchStmt>(Term), *this, B, Pred); + NodeBuilderContext Ctx(*this, B, Pred); + ExplodedNodeSet Dst; + ExprEng.processSwitch(Ctx, cast<SwitchStmt>(Term), Pred, Dst); + // Enqueue the new frontier onto the worklist. + for (auto *N : Dst) + WList->enqueue(N); return; } @@ -726,38 +742,22 @@ ExplodedNode *BranchNodeBuilder::generateNode(ProgramStateRef State, ExplodedNode *IndirectGotoNodeBuilder::generateNode(const CFGBlock *Block, ProgramStateRef St, - bool IsSink) { - bool IsNew; - ExplodedNode *Succ = Eng.G.getNode( - BlockEdge(Src, Block, Pred->getLocationContext()), St, IsSink, &IsNew); - Succ->addPredecessor(Pred, Eng.G); - - if (!IsNew) - return nullptr; - - if (!IsSink) - Eng.WList->enqueue(Succ); - - return Succ; + ExplodedNode *Pred) { + BlockEdge BE(C.getBlock(), Block, Pred->getLocationContext()); + return generateNode(BE, St, Pred); } ExplodedNode *SwitchNodeBuilder::generateCaseStmtNode(const CFGBlock *Block, - ProgramStateRef St) { - bool IsNew; - ExplodedNode *Succ = Eng.G.getNode( - BlockEdge(Src, Block, Pred->getLocationContext()), St, false, &IsNew); - Succ->addPredecessor(Pred, Eng.G); - if (!IsNew) - return nullptr; - - Eng.WList->enqueue(Succ); - return Succ; + ProgramStateRef St, + ExplodedNode *Pred) { + BlockEdge BE(C.getBlock(), Block, Pred->getLocationContext()); + return generateNode(BE, St, Pred); } -ExplodedNode* -SwitchNodeBuilder::generateDefaultCaseNode(ProgramStateRef St, - bool IsSink) { +ExplodedNode *SwitchNodeBuilder::generateDefaultCaseNode(ProgramStateRef St, + ExplodedNode *Pred) { // Get the block for the default case. + const CFGBlock *Src = C.getBlock(); assert(Src->succ_rbegin() != Src->succ_rend()); CFGBlock *DefaultBlock = *Src->succ_rbegin(); @@ -766,17 +766,6 @@ SwitchNodeBuilder::generateDefaultCaseNode(ProgramStateRef St, if (!DefaultBlock) return nullptr; - bool IsNew; - ExplodedNode *Succ = - Eng.G.getNode(BlockEdge(Src, DefaultBlock, Pred->getLocationContext()), - St, IsSink, &IsNew); - Succ->addPredecessor(Pred, Eng.G); - - if (!IsNew) - return nullptr; - - if (!IsSink) - Eng.WList->enqueue(Succ); - - return Succ; + BlockEdge BE(Src, DefaultBlock, Pred->getLocationContext()); + return generateNode(BE, St, Pred); } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 883c7b5d66c32..47312a53f61ff 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -2992,23 +2992,18 @@ void ExprEngine::processStaticInitializer( /// processIndirectGoto - Called by CoreEngine. Used to generate successor /// nodes by processing the 'effects' of a computed goto jump. -void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { - ProgramStateRef state = builder.getState(); - SVal V = state->getSVal(builder.getTarget(), builder.getLocationContext()); - - // Three possibilities: - // - // (1) We know the computed label. - // (2) The label is NULL (or some other constant), or Undefined. - // (3) We have no clue about the label. Dispatch to all targets. - // +void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &Builder, + ExplodedNode *Pred) { + ProgramStateRef State = Pred->getState(); + SVal V = State->getSVal(Builder.getTarget(), Builder.getLocationContext()); + // Case 1: We know the computed label. if (std::optional<loc::GotoLabel> LV = V.getAs<loc::GotoLabel>()) { const LabelDecl *L = LV->getLabel(); - for (const CFGBlock *Succ : builder) { + for (const CFGBlock *Succ : Builder) { if (cast<LabelStmt>(Succ->getLabel())->getDecl() == L) { - builder.generateNode(Succ, state); + Builder.generateNode(Succ, State, Pred); return; } } @@ -3016,19 +3011,16 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { llvm_unreachable("No block with label."); } + // Case 2: The label is NULL (or some other constant), or Undefined. if (isa<UndefinedVal, loc::ConcreteInt>(V)) { - // Dispatch to the first target and mark it as a sink. - //ExplodedNode* N = builder.generateNode(builder.begin(), state, true); - // FIXME: add checker visit. - // UndefBranches.insert(N); + // FIXME: Emit warnings when the jump target is undefined or numerical. return; } - // This is really a catch-all. We don't support symbolics yet. + // Case 3: We have no clue about the label. Dispatch to all targets. // FIXME: Implement dispatch for symbolic pointers. - - for (const CFGBlock *Succ : builder) - builder.generateNode(Succ, state); + for (const CFGBlock *Succ : Builder) + Builder.generateNode(Succ, State, Pred); } void ExprEngine::processBeginOfFunction(NodeBuilderContext &BC, @@ -3112,19 +3104,17 @@ 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(const SwitchStmt *Switch, CoreEngine &CoreEng, - const CFGBlock *B, ExplodedNode *Pred) { +void ExprEngine::processSwitch(NodeBuilderContext &BC, const SwitchStmt *Switch, + ExplodedNode *Pred, ExplodedNodeSet &Dst) { const Expr *Condition = Switch->getCond(); - SwitchNodeBuilder Builder(Pred, B, CoreEng); + SwitchNodeBuilder Builder(Pred, Dst, BC); 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); + // FIXME: Emit warnings when the switch condition is undefined. return; } @@ -3160,7 +3150,7 @@ void ExprEngine::processSwitch(const SwitchStmt *Switch, CoreEngine &CoreEng, } if (StateMatching) - Builder.generateCaseStmtNode(Block, StateMatching); + Builder.generateCaseStmtNode(Block, StateMatching, Pred); // If _not_ entering the current case is infeasible, we are done with // processing this branch. @@ -3179,7 +3169,7 @@ void ExprEngine::processSwitch(const SwitchStmt *Switch, CoreEngine &CoreEng, return; } - Builder.generateDefaultCaseNode(State); + Builder.generateDefaultCaseNode(State, Pred); } //===----------------------------------------------------------------------===// diff --git a/clang/test/Analysis/indirect-goto-basics.c b/clang/test/Analysis/indirect-goto-basics.c new file mode 100644 index 0000000000000..93ee5b39ce913 --- /dev/null +++ b/clang/test/Analysis/indirect-goto-basics.c @@ -0,0 +1,83 @@ +// RUN: %clang_analyze_cc1 -verify %s -analyzer-checker=core + +// This file tests ExprEngine::processIndirectGoto and the class +// IndirectGotoNodeBuilder. + +int goto_known_label_1(int x) { + // In this example the ternary operator splits the state, the analyzer can + // exactly follow the computed goto on both execution paths and validate that + // we reach "33 / x" with a state where x is constrained to zero. + void *target = x ? &&nonzero : &&zero; + goto *target; +zero: + return 33 / x; // expected-warning {{Division by zero}} +nonzero: + return 66 / (x + 1); +} + +int goto_known_label_2(int x) { + // In this example the ternary operator splits the state, the analyzer can + // exactly follow the computed goto on both execution paths and validate that + // we do not reach "66 / x" with the state where x is constrained to zero. + void *target = x ? &&nonzero : &&zero; + goto *target; +zero: + return 33 / (x + 1); +nonzero: + return 66 / x; +} + +void *select(int, void *, void *, void *); +int goto_symbolic(int x, int y) { + // In this example the target of the indirect goto is a symbolic value so the + // analyzer dispatches to all possible labels and we get the zero division + // errors at all of them. + void *target = select(x, &&first, &&second, &&third); + if (y) + return 41; + goto *target; +first: + return 33 / y; // expected-warning {{Division by zero}} +second: + return 66 / y; // expected-warning {{Division by zero}} +third: + return 123 / y; // expected-warning {{Division by zero}} +} + +int goto_nullpointer(int x, int y) { + // In this example the target of the indirect goto is a loc::ConcreteInt (a + // null pointer), so the analyzer doesn't dispatch anywhere. + // FIXME: We should emit a warning in this situation. + void *target = (void *)0; + (void)&&first; + (void)&&second; + (void)&&third; + if (y) + return 41; + goto *target; +first: + return 33 / y; +second: + return 66 / y; +third: + return 123 / y; +} + +int goto_undefined(int x, int y) { + // In this example the target of the indirect goto is an uninitialized + // pointer, so the analyzer doesn't dispatch anywhere. + // FIXME: We should emit a warning in this situation. + void *target; + (void)&&first; + (void)&&second; + (void)&&third; + if (y) + return 41; + goto *target; +first: + return 33 / y; +second: + return 66 / y; +third: + return 123 / y; +} diff --git a/clang/test/Analysis/switch-basics.c b/clang/test/Analysis/switch-basics.c new file mode 100644 index 0000000000000..2a0f9c2a3cf1b --- /dev/null +++ b/clang/test/Analysis/switch-basics.c @@ -0,0 +1,110 @@ +// RUN: %clang_analyze_cc1 -verify %s -analyzer-checker=core + +// This file tests ExprEngine::processSwitch and the class SwitchNodeBuilder. + +int switch_simple(int x) { + // Validate that switch behaves as expected in a very simple situation. + switch (x) { + case 1: + return 13 / x; + case 0: + return 14 / x; // expected-warning {{Division by zero}} + case 2: + return 15 / x; + default: + return 67 / (x - x); // expected-warning {{Division by zero}} + } +} + +int switch_default(int x) { + // Validate that the default case is evaluated after excluding the other + // cases -- even if it appears first. + if (x > 2 || x < 0) + return 0; + switch (x) { + default: + return 16 / x; // expected-warning {{Division by zero}} + case 1: + return 13 / x; + case 2: + return 15 / x; + } +} + +int switch_unreachable_default(int x) { + // Validate that the default case is not evaluated if it is infeasible. + int zero = 0; + if (x > 2 || x < 0) + return 0; + switch (x) { + default: + return 16 / zero; // no-warning + case 0: + return 456; + case 1: + return 13 / x; + case 2: + return 15 / x; + } +} + +enum Color {Red, Green, Blue}; + +int switch_all_enum_cases_covered(enum Color x) { + // Validate that the default case is not evaluated if the switch is over an + // enum value and all enumerators appear as 'case's. + int zero = 0; + switch (x) { + default: + return 16 / zero; // no-warning + case Red: + case Green: + return 2; + case Blue: + return 3; + } +} + +int switch_all_feasible_enum_cases_covered(enum Color x) { + // Highlight a shortcoming of enum/switch handling: here the 'case's cover + // all the enumerators that could appear in the symbolic value 'x', but the + // default is still executed. + // FIXME: The default branch shouldn't be executed here. + int zero = 0; + + if (x == Red) + return 1; + switch (x) { + default: + return 16 / zero; // expected-warning {{Division by zero}} + case Green: + return 2; + case Blue: + return 3; + } +} + +int switch_no_compound_stmt(int x) { + // Validate that the engine can follow the switch statement even if there is + // no compound statement around the cases. (Yes, this is valid, although + // not very practical.) + switch (x) case 1: case 0: return 16 / x; // expected-warning {{Division by zero}} + + return 0; +} + +int switch_with_case_range(int x) { + // Validate that the GNU case range extension is properly handled. + switch (x) { + case 5: + return 55 / x; + case 2 ... 4: + return 3; + case 0 ... 1: + return 44 / x; // no-warning: there is no state split between 0 and 1 + default: + if (x) + return 8; + return 45 / x; // no-warning: x cannot be 0 on the default branch + } +} From 9b8e1236cd8ea1147eb158530c60735ab64e8ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <[email protected]> Date: Thu, 12 Feb 2026 11:56:34 +0100 Subject: [PATCH 2/2] Remove debug printouts, simplify enqueueing --- clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index 8f087cc53aaa9..619e2dc58ca78 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -433,15 +433,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { ExprEng.processIndirectGoto(Builder, Pred); // Enqueue the new frontier onto the worklist. - llvm::errs() << "Pred location is "; - Pred->getLocation().dump(); - llvm::errs() << "\n"; - for (auto *N : Dst) { - llvm::errs() << "Enqueueing node at "; - N->getLocation().dump(); - llvm::errs() << "\n"; - WList->enqueue(N); - } + enqueue(Dst); return; } @@ -464,8 +456,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { ExplodedNodeSet Dst; ExprEng.processSwitch(Ctx, cast<SwitchStmt>(Term), Pred, Dst); // Enqueue the new frontier onto the worklist. - for (auto *N : Dst) - WList->enqueue(N); + enqueue(Dst); return; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
