From a0d20a24721eedab9103b6c6ba7f7b92185e65e3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 22 Jun 2026 12:26:03 -0500 Subject: [PATCH 01/12] Update to use stopOnCondition --- lib/forwardanalyzer.cpp | 2 +- lib/vf_analyzers.cpp | 34 +++++++++++++++--- test/testnullpointer.cpp | 75 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/lib/forwardanalyzer.cpp b/lib/forwardanalyzer.cpp index 0999a08d908..670db0b1090 100644 --- a/lib/forwardanalyzer.cpp +++ b/lib/forwardanalyzer.cpp @@ -782,7 +782,7 @@ namespace { } else if (thenBranch.check) { return Break(); } else { - if (analyzer->isConditional() && stopUpdates()) + if (stopOnCondition(condTok) && stopUpdates()) return Break(Analyzer::Terminate::Conditional); analyzer->assume(condTok, false); } diff --git a/lib/vf_analyzers.cpp b/lib/vf_analyzers.cpp index a50a3b1d4a4..dea1480a0c3 100644 --- a/lib/vf_analyzers.cpp +++ b/lib/vf_analyzers.cpp @@ -1198,18 +1198,44 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer { bool stopOnCondition(const Token* condTok) const override { - if (value.isNonValue()) - return false; if (value.isImpossible()) return false; - if (isConditional() && !value.isKnown()) + // Lifetime values must keep flowing through conditions to detect dangling dereferences on every path. + if (value.isLifetimeValue()) + return false; + // A value carrying the explicit 'conditional' flag (e.g. an uninitialized value, or a value lowered + // to possible after a branch that modifies the variable) can depend on conditions that don't mention + // the variable itself, so stop at any subsequent condition to stay conservative. + if (value.conditional && !value.isKnown()) return true; - if (value.isSymbolicValue()) + if (value.isNonValue()) return false; + if (value.isSymbolicValue()) + return isConditional() && !value.isKnown(); + // The value may still be conditional via the originating 'condition' token (e.g. a possible null + // pointer after 'if (p && ...)'). Such a value may keep flowing past a later condition, but only + // when that condition actually refers to the tracked value: then cppcheck can reason about how the + // condition constrains it. If the value is not mentioned, a correlation that cppcheck cannot follow + // during forward analysis (e.g. 'bool ok = (p != nullptr); if (!ok) return;') could make a later + // dereference safe, so stop conservatively to avoid false positives. + if (value.condition && !value.isKnown() && !conditionReferencesValue(condTok)) + return true; ConditionState cs = analyzeCondition(condTok); return cs.isUnknownDependent(); } + // Does the condition mention the tracked value, either directly or through a symbolic alias? + bool conditionReferencesValue(const Token* condTok) const + { + return findAstNode(condTok, [&](const Token* tok) { + if (match(tok)) + return true; + return std::any_of(tok->values().cbegin(), tok->values().cend(), [&](const ValueFlow::Value& v) { + return v.isSymbolicValue() && !v.isImpossible() && v.tokvalue && match(v.tokvalue); + }); + }) != nullptr; + } + bool updateScope(const Token* endBlock, bool /*modified*/) const override { const Scope* scope = endBlock->scope(); if (!scope) diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index 99ece1c57fc..b9b8b443b7a 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -143,6 +143,8 @@ class TestNullPointer : public TestFixture { TEST_CASE(nullpointer103); TEST_CASE(nullpointer104); // #13881 TEST_CASE(nullpointer105); // #13861 + TEST_CASE(nullpointer106); // #13682 + TEST_CASE(nullpointer107); // #13682 (no false positive past unrelated conditions) TEST_CASE(nullpointer_addressOf); // address of TEST_CASE(nullpointerSwitch); // #2626 TEST_CASE(nullpointer_cast); // #4692 @@ -2966,6 +2968,79 @@ class TestNullPointer : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void nullpointer106() // #13682 + { + // An unrelated condition between the null check and the dereference must not stop the analysis + check("struct S {\n" + " bool b;\n" + " bool f() const;\n" + "};\n" + "void f(const S* p, const S* o) {\n" + " const S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (p == o)\n" + " return;\n" + " if (p1->b) {}\n" + "}\n"); + ASSERT_EQUALS( + "[test.cpp:7:9] -> [test.cpp:11:9]: (warning) Either the condition 'p1' is redundant or there is possible null pointer dereference: p1. [nullPointerRedundantCheck]\n", + errout_str()); + } + + void nullpointer107() // #13682 - don't flow past a condition that doesn't reference the pointer + { + // The pointer's null-ness is cached in a boolean; the guard 'if (!ok)' makes the deref safe but + // cppcheck cannot follow that correlation during forward analysis -> must not warn (no false positive) + check("struct S { void g(); bool f() const; };\n" + "void f(S* p) {\n" + " bool ok = (p != nullptr);\n" + " if (p && p->f())\n" + " return;\n" + " if (!ok)\n" + " return;\n" + " p->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // A guard on an unrelated boolean (that the caller may use to imply validity) is not something + // cppcheck can follow -> stay conservative and do not warn + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, bool valid) {\n" + " S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (!valid)\n" + " return;\n" + " p1->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // A guard on a different pointer must not be assumed to constrain this one + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, S* q) {\n" + " S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (!q)\n" + " return;\n" + " p1->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // A direct null guard on the pointer (or its alias) is still handled and must not warn + check("struct S { void g(); bool f() const; };\n" + "void f(S* p) {\n" + " S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (!p)\n" + " return;\n" + " p1->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + void nullpointer_addressOf() { // address of check("void f() {\n" " struct X *x = 0;\n" From ca9522ad128dc23e1421945f2245665102351691 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 22 Jun 2026 12:34:26 -0500 Subject: [PATCH 02/12] TODO test case --- test/testnullpointer.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index b9b8b443b7a..581aa3e7a0f 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -145,6 +145,7 @@ class TestNullPointer : public TestFixture { TEST_CASE(nullpointer105); // #13861 TEST_CASE(nullpointer106); // #13682 TEST_CASE(nullpointer107); // #13682 (no false positive past unrelated conditions) + TEST_CASE(nullpointer108); // #13682 (FN: definite null deref missed due to ProgramMemory) TEST_CASE(nullpointer_addressOf); // address of TEST_CASE(nullpointerSwitch); // #2626 TEST_CASE(nullpointer_cast); // #4692 @@ -3041,6 +3042,27 @@ class TestNullPointer : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void nullpointer108() // #13682 - FN: dereference of a definitely-null pointer is missed + { + // 'if (ok) return;' means the surviving path has ok==false, i.e. p==nullptr, so 'p->g()' + // dereferences a null pointer. ProgramMemory cannot evaluate the cached 'ok' (== (p != nullptr)) + // during forward analysis, so the conditionReferencesValue() guard stops the analysis here and the + // definite null dereference is missed. This should warn once ProgramMemory can follow 'ok'. + check("struct S { void g(); bool f() const; };\n" + "void f(S* p) {\n" + " bool ok = (p != nullptr);\n" + " if (p && p->f())\n" + " return;\n" + " if (ok)\n" + " return;\n" + " p->g();\n" + "}\n"); + TODO_ASSERT_EQUALS( + "[test.cpp:4:9] -> [test.cpp:8:5]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", + "", + errout_str()); + } + void nullpointer_addressOf() { // address of check("void f() {\n" " struct X *x = 0;\n" From 878eb6374f66a70101e0731f9f49686c866f7a9d Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 22 Jun 2026 17:21:11 -0500 Subject: [PATCH 03/12] Combine tests --- test/testnullpointer.cpp | 62 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index 581aa3e7a0f..4e901e8e30f 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -145,7 +145,7 @@ class TestNullPointer : public TestFixture { TEST_CASE(nullpointer105); // #13861 TEST_CASE(nullpointer106); // #13682 TEST_CASE(nullpointer107); // #13682 (no false positive past unrelated conditions) - TEST_CASE(nullpointer108); // #13682 (FN: definite null deref missed due to ProgramMemory) + TEST_CASE(nullpointer109); // #13682 (no FP when guard depends on pointer via conditional modification) TEST_CASE(nullpointer_addressOf); // address of TEST_CASE(nullpointerSwitch); // #2626 TEST_CASE(nullpointer_cast); // #4692 @@ -3040,10 +3040,7 @@ class TestNullPointer : public TestFixture { " p1->g();\n" "}\n"); ASSERT_EQUALS("", errout_str()); - } - void nullpointer108() // #13682 - FN: dereference of a definitely-null pointer is missed - { // 'if (ok) return;' means the surviving path has ok==false, i.e. p==nullptr, so 'p->g()' // dereferences a null pointer. ProgramMemory cannot evaluate the cached 'ok' (== (p != nullptr)) // during forward analysis, so the conditionReferencesValue() guard stops the analysis here and the @@ -3061,6 +3058,63 @@ class TestNullPointer : public TestFixture { "[test.cpp:4:9] -> [test.cpp:8:5]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", "", errout_str()); + + // 'q' aliases 'p' (symbolic q==p), so the guard 'if (q)' constrains 'p'. Modifying 'q' via sink() + // drops the symbolic relationship; conditionReferencesValue() only inspects symbolic values, so it + // no longer sees that the guard relates to 'p', stops the analysis, and the possible null + // dereference of 'p' is missed. A more robust dependency check (not symbolic-only) should warn. + check("struct S { void g(); bool f() const; };\n" + "void sink(S*&);\n" + "void f(S* p) {\n" + " S* q = p;\n" + " if (p && p->f())\n" + " return;\n" + " sink(q);\n" + " if (q)\n" + " return;\n" + " p->g();\n" + "}\n"); + TODO_ASSERT_EQUALS( + "[test.cpp:5:9] -> [test.cpp:10:5]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", + "", + errout_str()); + } + + void nullpointer109() // #13682 - no false positive when a guard depends on the pointer through a conditional modification + { + // These are dereferences that are actually safe, but the dependency that makes them safe is hidden + // behind a conditional modification. ProgramMemory tracks 'q'/'ok' and would normally evaluate the + // guard, but a conditional modification ('if (c) ...') makes it stop tracking the value, so the guard + // can no longer be evaluated during forward analysis. Without conditionReferencesValue() the possible + // null value would flow past the guard and produce a false positive; these must stay warning-free. + + // symbolic substitution: 'q == p', conditionally re-assigned to 'p' (value preserved) + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, bool c) {\n" + " S* q = p;\n" + " if (p && p->f())\n" + " return;\n" + " if (c)\n" + " q = p;\n" + " if (!q)\n" + " return;\n" + " p->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // conditional assignment of a cached condition: 'ok == (p != nullptr)', conditionally refreshed + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, bool c) {\n" + " bool ok = (p != nullptr);\n" + " if (p && p->f())\n" + " return;\n" + " if (c)\n" + " ok = (p != nullptr);\n" + " if (!ok)\n" + " return;\n" + " p->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void nullpointer_addressOf() { // address of From 12496fb3fadcb81b77d3e357ec94a468c26dac82 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 22 Jun 2026 17:55:58 -0500 Subject: [PATCH 04/12] Reduce the test cases --- test/testnullpointer.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index 4e901e8e30f..da5ece72ba2 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -144,8 +144,7 @@ class TestNullPointer : public TestFixture { TEST_CASE(nullpointer104); // #13881 TEST_CASE(nullpointer105); // #13861 TEST_CASE(nullpointer106); // #13682 - TEST_CASE(nullpointer107); // #13682 (no false positive past unrelated conditions) - TEST_CASE(nullpointer109); // #13682 (no FP when guard depends on pointer via conditional modification) + TEST_CASE(nullpointer107); // #13682 (FP/FN cases around guards that depend on the pointer indirectly) TEST_CASE(nullpointer_addressOf); // address of TEST_CASE(nullpointerSwitch); // #2626 TEST_CASE(nullpointer_cast); // #4692 @@ -3078,10 +3077,7 @@ class TestNullPointer : public TestFixture { "[test.cpp:5:9] -> [test.cpp:10:5]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", "", errout_str()); - } - void nullpointer109() // #13682 - no false positive when a guard depends on the pointer through a conditional modification - { // These are dereferences that are actually safe, but the dependency that makes them safe is hidden // behind a conditional modification. ProgramMemory tracks 'q'/'ok' and would normally evaluate the // guard, but a conditional modification ('if (c) ...') makes it stop tracking the value, so the guard From 0fa37d5fdc328c5d3e8d89bbe92c0f6a0dbfba4c Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 22 Jun 2026 17:58:55 -0500 Subject: [PATCH 05/12] Terser comments --- test/testnullpointer.cpp | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index da5ece72ba2..369c04a2101 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -2988,10 +2988,9 @@ class TestNullPointer : public TestFixture { errout_str()); } - void nullpointer107() // #13682 - don't flow past a condition that doesn't reference the pointer + void nullpointer107() // #13682 - guards that depend on the pointer indirectly { - // The pointer's null-ness is cached in a boolean; the guard 'if (!ok)' makes the deref safe but - // cppcheck cannot follow that correlation during forward analysis -> must not warn (no false positive) + // cached null-check 'ok'; guard 'if (!ok)' is safe -> no FP check("struct S { void g(); bool f() const; };\n" "void f(S* p) {\n" " bool ok = (p != nullptr);\n" @@ -3003,8 +3002,7 @@ class TestNullPointer : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - // A guard on an unrelated boolean (that the caller may use to imply validity) is not something - // cppcheck can follow -> stay conservative and do not warn + // unrelated bool guard -> conservative, no FP check("struct S { void g(); bool f() const; };\n" "void f(S* p, bool valid) {\n" " S* p1 = p;\n" @@ -3016,7 +3014,7 @@ class TestNullPointer : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - // A guard on a different pointer must not be assumed to constrain this one + // guard on a different pointer -> no FP check("struct S { void g(); bool f() const; };\n" "void f(S* p, S* q) {\n" " S* p1 = p;\n" @@ -3028,7 +3026,7 @@ class TestNullPointer : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - // A direct null guard on the pointer (or its alias) is still handled and must not warn + // direct null guard on the alias -> no FP check("struct S { void g(); bool f() const; };\n" "void f(S* p) {\n" " S* p1 = p;\n" @@ -3040,10 +3038,7 @@ class TestNullPointer : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - // 'if (ok) return;' means the surviving path has ok==false, i.e. p==nullptr, so 'p->g()' - // dereferences a null pointer. ProgramMemory cannot evaluate the cached 'ok' (== (p != nullptr)) - // during forward analysis, so the conditionReferencesValue() guard stops the analysis here and the - // definite null dereference is missed. This should warn once ProgramMemory can follow 'ok'. + // FN: 'if (ok)' => survivor has p==nullptr, but the cached 'ok' is not followed -> should warn check("struct S { void g(); bool f() const; };\n" "void f(S* p) {\n" " bool ok = (p != nullptr);\n" @@ -3058,10 +3053,7 @@ class TestNullPointer : public TestFixture { "", errout_str()); - // 'q' aliases 'p' (symbolic q==p), so the guard 'if (q)' constrains 'p'. Modifying 'q' via sink() - // drops the symbolic relationship; conditionReferencesValue() only inspects symbolic values, so it - // no longer sees that the guard relates to 'p', stops the analysis, and the possible null - // dereference of 'p' is missed. A more robust dependency check (not symbolic-only) should warn. + // FN: sink(q) drops the q==p symbolic, so guard 'if (q)' is no longer seen to relate to p -> should warn check("struct S { void g(); bool f() const; };\n" "void sink(S*&);\n" "void f(S* p) {\n" @@ -3078,13 +3070,8 @@ class TestNullPointer : public TestFixture { "", errout_str()); - // These are dereferences that are actually safe, but the dependency that makes them safe is hidden - // behind a conditional modification. ProgramMemory tracks 'q'/'ok' and would normally evaluate the - // guard, but a conditional modification ('if (c) ...') makes it stop tracking the value, so the guard - // can no longer be evaluated during forward analysis. Without conditionReferencesValue() the possible - // null value would flow past the guard and produce a false positive; these must stay warning-free. - - // symbolic substitution: 'q == p', conditionally re-assigned to 'p' (value preserved) + // a conditional modification makes ProgramMemory drop the guard (FP-prone) -> must stay quiet: + // alias 'q==p' re-assigned to p under a condition check("struct S { void g(); bool f() const; };\n" "void f(S* p, bool c) {\n" " S* q = p;\n" @@ -3098,7 +3085,7 @@ class TestNullPointer : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - // conditional assignment of a cached condition: 'ok == (p != nullptr)', conditionally refreshed + // cached 'ok' refreshed under a condition check("struct S { void g(); bool f() const; };\n" "void f(S* p, bool c) {\n" " bool ok = (p != nullptr);\n" From c9912bc89b93c0afaf6e86e122329b4afbeb084b Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 22 Jun 2026 18:00:59 -0500 Subject: [PATCH 06/12] Terser comments --- lib/vf_analyzers.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/vf_analyzers.cpp b/lib/vf_analyzers.cpp index dea1480a0c3..1810cab36d8 100644 --- a/lib/vf_analyzers.cpp +++ b/lib/vf_analyzers.cpp @@ -1200,24 +1200,20 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer { { if (value.isImpossible()) return false; - // Lifetime values must keep flowing through conditions to detect dangling dereferences on every path. + // lifetime values must keep flowing to find dangling derefs on all paths if (value.isLifetimeValue()) return false; - // A value carrying the explicit 'conditional' flag (e.g. an uninitialized value, or a value lowered - // to possible after a branch that modifies the variable) can depend on conditions that don't mention - // the variable itself, so stop at any subsequent condition to stay conservative. + // 'conditional' flag (uninit, or lowered after a modifying branch): may depend on a + // condition that doesn't mention the variable -> stop if (value.conditional && !value.isKnown()) return true; if (value.isNonValue()) return false; if (value.isSymbolicValue()) return isConditional() && !value.isKnown(); - // The value may still be conditional via the originating 'condition' token (e.g. a possible null - // pointer after 'if (p && ...)'). Such a value may keep flowing past a later condition, but only - // when that condition actually refers to the tracked value: then cppcheck can reason about how the - // condition constrains it. If the value is not mentioned, a correlation that cppcheck cannot follow - // during forward analysis (e.g. 'bool ok = (p != nullptr); if (!ok) return;') could make a later - // dereference safe, so stop conservatively to avoid false positives. + // conditional via the originating 'condition' (e.g. possible null after 'if (p && ...)'): only flow + // if the condition references the value, else a correlation we can't follow (e.g. + // 'bool ok = (p != nullptr); if (!ok)') could make a later deref safe -> stop if (value.condition && !value.isKnown() && !conditionReferencesValue(condTok)) return true; ConditionState cs = analyzeCondition(condTok); From 3cea3ef6625d294dd78f6d5e1a09a61608b7cbe2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 22 Jun 2026 18:02:48 -0500 Subject: [PATCH 07/12] Make the comment clearer --- lib/vf_analyzers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vf_analyzers.cpp b/lib/vf_analyzers.cpp index 1810cab36d8..10efc6d6f39 100644 --- a/lib/vf_analyzers.cpp +++ b/lib/vf_analyzers.cpp @@ -1200,7 +1200,7 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer { { if (value.isImpossible()) return false; - // lifetime values must keep flowing to find dangling derefs on all paths + // lifetime values must keep flowing to properly track aliases if (value.isLifetimeValue()) return false; // 'conditional' flag (uninit, or lowered after a modifying branch): may depend on a From 6fa028a4000346c409f4a911024c1cf0d3f158d5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 23 Jun 2026 08:23:24 -0500 Subject: [PATCH 08/12] Fix cppcheck warnings --- lib/astutils.cpp | 4 ---- lib/checkclass.cpp | 3 +-- lib/checkcondition.cpp | 2 +- lib/checkstl.cpp | 2 +- lib/reverseanalyzer.cpp | 3 --- lib/symboldatabase.cpp | 6 ++---- lib/tokenize.cpp | 2 +- 7 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 8dfd03596d3..f05f875d40a 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -495,8 +495,6 @@ bool isTemporary(const Token* tok, const Library* library, bool unknown) } return unknown; } - if (tok->isCast()) - return false; // Currying a function is unknown in cppcheck if (Token::simpleMatch(tok, "(") && Token::simpleMatch(tok->astOperand1(), "(")) return unknown; @@ -1543,8 +1541,6 @@ bool isUsedAsBool(const Token* const tok, const Settings& settings) return true; if (parent->isCast()) return !Token::simpleMatch(parent->astOperand1(), "dynamic_cast") && isUsedAsBool(parent, settings); - if (parent->isUnaryOp("*")) - return isUsedAsBool(parent, settings); if (Token::Match(parent, "==|!=") && tok->valueType() && tok->valueType()->pointer && tok->astSibling()->hasKnownIntValue() && tok->astSibling()->getKnownIntValue() == 0) return true; diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index 7d3fa5c51ec..3a5fbbc6992 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -3452,8 +3452,7 @@ void CheckClassImpl::checkUselessOverride() if (isSameCode) { // bailout for shadowed members - if (!classScope->definedType || - !getDuplInheritedMembersRecursive(classScope->definedType, classScope->definedType, /*skipPrivate*/ false).empty() || + if (!getDuplInheritedMembersRecursive(classScope->definedType, classScope->definedType, /*skipPrivate*/ false).empty() || !getDuplInheritedMemberFunctionsRecursive(classScope->definedType, classScope->definedType, /*skipPrivate*/ false).empty()) continue; uselessOverrideError(baseFunc, &func, true); diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index 72b61fd68b9..e77641f89e7 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -455,7 +455,7 @@ bool CheckConditionImpl::isOverlappingCond(const Token * const cond1, const Toke if (!num1->isNumber() || MathLib::isNegative(num1->str())) return false; - if (!Token::Match(cond2, "&|==") || !cond2->astOperand1() || !cond2->astOperand2()) + if (!Token::Match(cond2, "&|==") || !cond2->astOperand1()) return false; const Token *expr2 = cond2->astOperand1(); const Token *num2 = cond2->astOperand2(); diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp index f499bb03b91..6e4c226e1d9 100644 --- a/lib/checkstl.cpp +++ b/lib/checkstl.cpp @@ -549,7 +549,7 @@ void CheckStlImpl::iterators() } // Not different containers if a reference is used.. - if (containerToken && containerToken->variable() && containerToken->variable()->isReference()) { + if (containerToken->variable() && containerToken->variable()->isReference()) { const Token *nameToken = containerToken->variable()->nameToken(); if (Token::Match(nameToken, "%name% =")) { const Token *name1 = nameToken->tokAt(2); diff --git a/lib/reverseanalyzer.cpp b/lib/reverseanalyzer.cpp index 4afd2556a24..10f178abe50 100644 --- a/lib/reverseanalyzer.cpp +++ b/lib/reverseanalyzer.cpp @@ -340,9 +340,6 @@ namespace { valueFlowGenericForward(condTok, analyzer, tokenlist, errorLogger, settings); else if (condAction.isRead()) break; - // If the condition modifies the variable then bail - if (condAction.isModified()) - break; tok = jumpToStart(tok->link()); continue; } diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index ba5c31d1f47..42fc855d5e9 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -1043,10 +1043,8 @@ void SymbolDatabase::createSymbolDatabaseNeedInitialization() scope.definedType->needInitialization = Type::NeedInitialization::True; else if (!unknown) scope.definedType->needInitialization = Type::NeedInitialization::False; - else { - if (scope.definedType->needInitialization == Type::NeedInitialization::Unknown) - unknowns++; - } + else + unknowns++; } } else if (scope.type == ScopeType::eUnion && scope.definedType->needInitialization == Type::NeedInitialization::Unknown) scope.definedType->needInitialization = Type::NeedInitialization::True; diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index a8f675bbc97..7379957804f 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -2711,7 +2711,7 @@ namespace { { Token *tok1 = tok; - if (tok1 && tok1->str() != nameToken->str()) + if (!tok1 || tok1->str() != nameToken->str()) return false; // skip this using From 086a98c68e3179df673f37c3f5fb8f69dc9fb764 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 23 Jun 2026 08:23:41 -0500 Subject: [PATCH 09/12] Format --- lib/checkclass.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index 3a5fbbc6992..29322af4ac2 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -3452,8 +3452,14 @@ void CheckClassImpl::checkUselessOverride() if (isSameCode) { // bailout for shadowed members - if (!getDuplInheritedMembersRecursive(classScope->definedType, classScope->definedType, /*skipPrivate*/ false).empty() || - !getDuplInheritedMemberFunctionsRecursive(classScope->definedType, classScope->definedType, /*skipPrivate*/ false).empty()) + if (!getDuplInheritedMembersRecursive(classScope->definedType, + classScope->definedType, + /*skipPrivate*/ false) + .empty() || + !getDuplInheritedMemberFunctionsRecursive(classScope->definedType, + classScope->definedType, + /*skipPrivate*/ false) + .empty()) continue; uselessOverrideError(baseFunc, &func, true); continue; From e2e5ca26d3f350fc016093b2f9c8d6c7f78a1701 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 23 Jun 2026 09:32:58 -0500 Subject: [PATCH 10/12] Remove dead branch --- externals/simplecpp/simplecpp.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/externals/simplecpp/simplecpp.cpp b/externals/simplecpp/simplecpp.cpp index 47e674837ba..a42ca4ae853 100644 --- a/externals/simplecpp/simplecpp.cpp +++ b/externals/simplecpp/simplecpp.cpp @@ -2332,9 +2332,6 @@ namespace simplecpp { const Token *nextTok = B->next; if (canBeConcatenatedStringOrChar) { - if (unexpectedA) - throw invalidHashHash::unexpectedToken(tok->location, name(), A); - // It seems clearer to handle this case separately even though the code is similar-ish, but we don't want to merge here. // TODO The question is whether the ## or varargs may still apply, and how to provoke? if (expandArg(tokensB, B, parametertokens)) { From 990c969d8f58bb3925d94d4db73dfe15bff65372 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 23 Jun 2026 10:42:35 -0500 Subject: [PATCH 11/12] Revert "Remove dead branch" This reverts commit e2e5ca26d3f350fc016093b2f9c8d6c7f78a1701. --- externals/simplecpp/simplecpp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/externals/simplecpp/simplecpp.cpp b/externals/simplecpp/simplecpp.cpp index a42ca4ae853..47e674837ba 100644 --- a/externals/simplecpp/simplecpp.cpp +++ b/externals/simplecpp/simplecpp.cpp @@ -2332,6 +2332,9 @@ namespace simplecpp { const Token *nextTok = B->next; if (canBeConcatenatedStringOrChar) { + if (unexpectedA) + throw invalidHashHash::unexpectedToken(tok->location, name(), A); + // It seems clearer to handle this case separately even though the code is similar-ish, but we don't want to merge here. // TODO The question is whether the ## or varargs may still apply, and how to provoke? if (expandArg(tokensB, B, parametertokens)) { From 10f035ac3031ba88406bb01c90f36734ad37314b Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 23 Jun 2026 10:44:01 -0500 Subject: [PATCH 12/12] Add suppression --- .selfcheck_suppressions | 1 + 1 file changed, 1 insertion(+) diff --git a/.selfcheck_suppressions b/.selfcheck_suppressions index f21492e783a..402fdbeb75d 100644 --- a/.selfcheck_suppressions +++ b/.selfcheck_suppressions @@ -79,3 +79,4 @@ useStlAlgorithm:externals/simplecpp/simplecpp.cpp funcArgNamesDifferentUnnamed:externals/simplecpp/simplecpp.h missingMemberCopy:externals/simplecpp/simplecpp.h shadowFunction:externals/simplecpp/simplecpp.h +knownConditionTrueFalse:externals/simplecpp/simplecpp.cpp \ No newline at end of file