From f4ca315d9a6f892694e800e565db4d94c774f3fb Mon Sep 17 00:00:00 2001 From: Jeewoong Kim Date: Sat, 29 Aug 2026 21:32:19 +0900 Subject: [PATCH 1/3] Detect unselectable switch cases --- lib/checkother.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++ lib/checkother.h | 4 +++ test/testother.cpp | 56 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 7f22e531230..62636154b1d 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -968,6 +968,66 @@ void CheckOtherImpl::suspiciousCaseInSwitchError(const Token* tok, const std::st "Using an operator like '" + operatorString + "' in a case label is suspicious. Did you intend to use a bitwise operator, multiple case labels or if/else instead?", CWE398, Certainty::inconclusive); } +void CheckOtherImpl::checkUnreachableSwitchCase() +{ + if (!mSettings.severity.isEnabled(Severity::style)) + return; + + logChecker("CheckOther::checkUnreachableSwitchCase"); // style + + const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); + + for (const Scope& scope : symbolDatabase->scopeList) { + if (scope.type != ScopeType::eSwitch || !scope.bodyStart) + continue; + const Token* rpar = scope.bodyStart->previous(); + if (!Token::simpleMatch(rpar, ")")) + continue; + const Token* lpar = rpar->link(); + if (!lpar) + continue; + const Token* condition = lpar->astOperand2(); + if (!condition) + continue; + const ValueFlow::Value* switchValue = + condition->getKnownValue(ValueFlow::Value::ValueType::INT); + if (!switchValue) + continue; + + for (const Token* tok = scope.bodyStart->next(); + tok && tok != scope.bodyEnd; + tok = tok->next()) { + + // Do not inspect cases belonging to a nested switch. + if (Token::simpleMatch(tok, "{") && + tok->scope()->type == ScopeType::eSwitch) { + tok = tok->link(); + continue; + } + if (!Token::simpleMatch(tok, "case")) + continue; + const Token* caseExpression = tok->astOperand1(); + if (!caseExpression) + continue; + const ValueFlow::Value* caseValue = + caseExpression->getKnownValue(ValueFlow::Value::ValueType::INT); + if (!caseValue) + continue; + if (switchValue->intvalue == caseValue->intvalue) + continue; + unreachableSwitchCaseError(tok, caseExpression->expressionString()); + } + } +} + +void CheckOtherImpl::unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression) +{ + reportError(tok, Severity::style, "unreachableSwitchCase", + "Switch case '" + caseExpression + + "' can never be selected because the switch condition has a known value.", + CWE561, Certainty::normal); +} + static bool isNestedInSwitch(const Scope* scope) { while (scope) { @@ -4820,6 +4880,7 @@ void CheckOther::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) checkOther.checkCharVariable(); checkOther.redundantBitwiseOperationInSwitchError(); checkOther.checkSuspiciousCaseInSwitch(); + checkOther.checkUnreachableSwitchCase(); checkOther.checkDuplicateBranch(); checkOther.checkDuplicateExpression(); checkOther.checkRedundantAssignment(); @@ -4907,6 +4968,7 @@ void CheckOther::getErrorMessages(ErrorLogger& errorLogger, const Settings &sett c.duplicateExpressionTernaryError(nullptr, ErrorPath{}); c.duplicateBreakError(nullptr, false); c.unreachableCodeError(nullptr, nullptr, false); + c.unreachableSwitchCaseError(nullptr, "case"); c.unsignedLessThanZeroError(nullptr, nullptr, "varname"); c.unsignedPositiveError(nullptr, nullptr, "varname"); c.pointerLessThanZeroError(nullptr, nullptr); diff --git a/lib/checkother.h b/lib/checkother.h index caf9f4a9e57..3de7f046089 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -186,6 +186,9 @@ class CPPCHECKLIB CheckOtherImpl : public CheckImpl { /** @brief %Check for code like 'case A||B:'*/ void checkSuspiciousCaseInSwitch(); + /** @brief %Check for case labels that cannot be selected */ + void checkUnreachableSwitchCase(); + /** @brief %Check for objects that are destroyed immediately */ void checkMisusedScopedObject(); @@ -290,6 +293,7 @@ class CPPCHECKLIB CheckOtherImpl : public CheckImpl { void redundantCopyError(const Token *tok1, const Token* tok2, const std::string& var); void redundantBitwiseOperationInSwitchError(const Token *tok, const std::string &varname); void suspiciousCaseInSwitchError(const Token* tok, const std::string& operatorString); + void unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression); void selfAssignmentError(const Token *tok, const std::string &varname); void misusedScopeObjectError(const Token *tok, const std::string &varname, bool isAssignment = false); void duplicateBranchError(const Token *tok1, const Token *tok2, ErrorPath errors); diff --git a/test/testother.cpp b/test/testother.cpp index 3c301a3fc3c..ebcab503565 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -142,6 +142,7 @@ class TestOther : public TestFixture { TEST_CASE(switchRedundantOperationTest); TEST_CASE(switchRedundantBitwiseOperationTest); TEST_CASE(unreachableCode); + TEST_CASE(unreachableSwitchCase); // #8442 TEST_CASE(redundantContinue); TEST_CASE(suspiciousCase); @@ -6348,6 +6349,61 @@ class TestOther : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void unreachableSwitchCase() { + check("enum T { A, B};\n" + "void f(const T &t) {\n" + " if (t == A) {\n" + " switch (t) {\n" + " case A:\n" + " break;\n" + " case B:\n" + " break;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:7:9]: (style) Switch case 'B' can never be selected because the switch condition has a known value. [unreachableSwitchCase]\n", errout_str()); + + check("void f(int t) {\n" + " if (t == 0) {\n" + " switch (t) {\n" + " case 0:\n" + " break;\n" + " case 1:\n" + " break;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:9]: (style) Switch case '1' can never be selected because the switch condition has a known value. [unreachableSwitchCase]\n", errout_str()); + + check("void f(int t) {\n" + " switch (t) {\n" + " case 0:\n" + " break;\n" + " case 1:\n" + " break;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(int x, int y) {\n" + " if (x == 0) {\n" + " switch (x) {\n" + " case 0:\n" + " switch (y) {\n" + " case 1:\n" + " break;\n" + " case 2:\n" + " break;\n" + " }\n" + " break;\n" + " case 1:\n" + " break;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:12:9]: (style) Switch case '1' can never be selected because the switch condition has a known value. [unreachableSwitchCase]\n", errout_str()); + } + void redundantContinue() { check("void f() {\n" // #11195 " for (int i = 0; i < 10; ++i) {\n" From 20ff103a5f1ad4d5fc61e19b594f9359f394e921 Mon Sep 17 00:00:00 2001 From: Jeewoong Kim Date: Sun, 30 Aug 2026 17:51:29 +0900 Subject: [PATCH 2/3] Fix formatting --- lib/checkother.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 62636154b1d..9bf4512add9 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -998,24 +998,24 @@ void CheckOtherImpl::checkUnreachableSwitchCase() tok && tok != scope.bodyEnd; tok = tok->next()) { - // Do not inspect cases belonging to a nested switch. - if (Token::simpleMatch(tok, "{") && - tok->scope()->type == ScopeType::eSwitch) { - tok = tok->link(); - continue; - } - if (!Token::simpleMatch(tok, "case")) - continue; - const Token* caseExpression = tok->astOperand1(); - if (!caseExpression) - continue; - const ValueFlow::Value* caseValue = - caseExpression->getKnownValue(ValueFlow::Value::ValueType::INT); - if (!caseValue) - continue; - if (switchValue->intvalue == caseValue->intvalue) - continue; - unreachableSwitchCaseError(tok, caseExpression->expressionString()); + // Do not inspect cases belonging to a nested switch. + if (Token::simpleMatch(tok, "{") && + tok->scope()->type == ScopeType::eSwitch) { + tok = tok->link(); + continue; + } + if (!Token::simpleMatch(tok, "case")) + continue; + const Token* caseExpression = tok->astOperand1(); + if (!caseExpression) + continue; + const ValueFlow::Value* caseValue = + caseExpression->getKnownValue(ValueFlow::Value::ValueType::INT); + if (!caseValue) + continue; + if (switchValue->intvalue == caseValue->intvalue) + continue; + unreachableSwitchCaseError(tok, caseExpression->expressionString()); } } } From 2921054fd8a7608c9f7be72d307db718bd1e8f00 Mon Sep 17 00:00:00 2001 From: Jeewoong Kim Date: Mon, 31 Aug 2026 16:49:40 +0900 Subject: [PATCH 3/3] Include known value in unreachable switch case error message --- lib/checkother.cpp | 8 ++++---- lib/checkother.h | 2 +- releasenotes.txt | 1 + test/testother.cpp | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 9bf4512add9..0aa9dd18a84 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1015,16 +1015,16 @@ void CheckOtherImpl::checkUnreachableSwitchCase() continue; if (switchValue->intvalue == caseValue->intvalue) continue; - unreachableSwitchCaseError(tok, caseExpression->expressionString()); + unreachableSwitchCaseError(tok, caseExpression->expressionString(), MathLib::toString(switchValue->intvalue)); } } } -void CheckOtherImpl::unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression) +void CheckOtherImpl::unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression, const std::string& switchValue) { reportError(tok, Severity::style, "unreachableSwitchCase", "Switch case '" + caseExpression + - "' can never be selected because the switch condition has a known value.", + "' can never be selected because the switch condition is known to be " + switchValue + ".", CWE561, Certainty::normal); } @@ -4968,7 +4968,7 @@ void CheckOther::getErrorMessages(ErrorLogger& errorLogger, const Settings &sett c.duplicateExpressionTernaryError(nullptr, ErrorPath{}); c.duplicateBreakError(nullptr, false); c.unreachableCodeError(nullptr, nullptr, false); - c.unreachableSwitchCaseError(nullptr, "case"); + c.unreachableSwitchCaseError(nullptr, "case", "0"); c.unsignedLessThanZeroError(nullptr, nullptr, "varname"); c.unsignedPositiveError(nullptr, nullptr, "varname"); c.pointerLessThanZeroError(nullptr, nullptr); diff --git a/lib/checkother.h b/lib/checkother.h index 3de7f046089..c9f7a1f9530 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -293,7 +293,7 @@ class CPPCHECKLIB CheckOtherImpl : public CheckImpl { void redundantCopyError(const Token *tok1, const Token* tok2, const std::string& var); void redundantBitwiseOperationInSwitchError(const Token *tok, const std::string &varname); void suspiciousCaseInSwitchError(const Token* tok, const std::string& operatorString); - void unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression); + void unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression, const std::string& switchValue); void selfAssignmentError(const Token *tok, const std::string &varname); void misusedScopeObjectError(const Token *tok, const std::string &varname, bool isAssignment = false); void duplicateBranchError(const Token *tok1, const Token *tok2, ErrorPath errors); diff --git a/releasenotes.txt b/releasenotes.txt index 4b3ef047c5c..2b3f4865397 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -8,6 +8,7 @@ New checks: - Warn when feof() is used as a while loop condition (wrongfeofUsage). - ftell() result is unspecified when file is opened in mode "t". - Detect when an STL algorithm such as std::copy, std::equal, std::transform, etc. accesses more elements through an iterator than are available in the container (algorithmOutOfBounds). +- Detect switch cases that cannot be selected when the switch condition has a known value (unreachableSwitchCase). C/C++ support: - diff --git a/test/testother.cpp b/test/testother.cpp index ebcab503565..27f7700bd54 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -6361,7 +6361,7 @@ class TestOther : public TestFixture { " }\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:7:9]: (style) Switch case 'B' can never be selected because the switch condition has a known value. [unreachableSwitchCase]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:7:9]: (style) Switch case 'B' can never be selected because the switch condition is known to be 0. [unreachableSwitchCase]\n", errout_str()); check("void f(int t) {\n" " if (t == 0) {\n" @@ -6373,7 +6373,7 @@ class TestOther : public TestFixture { " }\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:6:9]: (style) Switch case '1' can never be selected because the switch condition has a known value. [unreachableSwitchCase]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:6:9]: (style) Switch case '1' can never be selected because the switch condition is known to be 0. [unreachableSwitchCase]\n", errout_str()); check("void f(int t) {\n" " switch (t) {\n" @@ -6401,7 +6401,7 @@ class TestOther : public TestFixture { " }\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:12:9]: (style) Switch case '1' can never be selected because the switch condition has a known value. [unreachableSwitchCase]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:12:9]: (style) Switch case '1' can never be selected because the switch condition is known to be 0. [unreachableSwitchCase]\n", errout_str()); } void redundantContinue() {