From 6c32577de08bea983c258827e66a75a54dff412c Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Thu, 19 Feb 2026 15:40:10 +0100 Subject: [PATCH 1/4] Fix #13944: FN constParameterPointer in method in derived class Add inconclusive and default=false (instead of true) in checkConstPointer. --- lib/checkother.cpp | 9 +++++---- lib/checkother.h | 2 +- lib/symboldatabase.cpp | 9 +++++++-- lib/symboldatabase.h | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index cf5ecb65b91..9f86296d0fe 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1996,8 +1996,9 @@ void CheckOther::checkConstPointer() nonConstPointers.emplace(var); } for (const Variable *p: pointers) { + bool inconclusive = false; if (p->isArgument()) { - if (!p->scope() || !p->scope()->function || p->scope()->function->isImplicitlyVirtual(true) || p->scope()->function->hasVirtualSpecifier()) + if (!p->scope() || !p->scope()->function || p->scope()->function->isImplicitlyVirtual(false, (bool*)nullptr, &inconclusive) || p->scope()->function->hasVirtualSpecifier()) continue; if (p->isMaybeUnused()) continue; @@ -2014,12 +2015,12 @@ void CheckOther::checkConstPointer() continue; if (p->typeStartToken() && p->typeStartToken()->isSimplifiedTypedef() && !(Token::simpleMatch(p->typeEndToken(), "*") && !p->typeEndToken()->isSimplifiedTypedef())) continue; - constVariableError(p, p->isArgument() ? p->scope()->function : nullptr); + constVariableError(p, p->isArgument() ? p->scope()->function : nullptr, &inconclusive); } } } -void CheckOther::constVariableError(const Variable *var, const Function *function) +void CheckOther::constVariableError(const Variable *var, const Function *function, bool* inconclusive) { if (!var) { reportError(nullptr, Severity::style, "constParameter", "Parameter 'x' can be declared with const"); @@ -2050,7 +2051,7 @@ void CheckOther::constVariableError(const Variable *var, const Function *functio id += "Pointer"; } - reportError(std::move(errorPath), Severity::style, id.c_str(), message, CWE398, Certainty::normal); + reportError(std::move(errorPath), Severity::style, id.c_str(), message, CWE398, (inconclusive && *inconclusive) ? Certainty::inconclusive : Certainty::normal); } //--------------------------------------------------------------------------- diff --git a/lib/checkother.h b/lib/checkother.h index 28ee25cfa76..e8b6c7d1d75 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -215,7 +215,7 @@ class CPPCHECKLIB CheckOther : public Check { void suspiciousFloatingPointCastError(const Token *tok); void invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt); void passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor = false); - void constVariableError(const Variable *var, const Function *function); + void constVariableError(const Variable *var, const Function *function, bool * inconclusive = nullptr); void constStatementError(const Token *tok, const std::string &type, bool inconclusive); void signedCharArrayIndexError(const Token *tok); void unknownSignCharArrayIndexError(const Token *tok); diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index f5ffa4e1604..28406942479 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -4810,8 +4810,10 @@ void Function::addArguments(const Scope *scope) } } -bool Function::isImplicitlyVirtual(bool defaultVal, bool* pFoundAllBaseClasses) const +bool Function::isImplicitlyVirtual(bool defaultVal, bool* pFoundAllBaseClasses, bool* inconclusive) const { + if (inconclusive) + *inconclusive = false; // assume not inconclusive. if (hasVirtualSpecifier() || hasOverrideSpecifier() || hasFinalSpecifier()) return true; bool foundAllBaseClasses = true; @@ -4821,7 +4823,10 @@ bool Function::isImplicitlyVirtual(bool defaultVal, bool* pFoundAllBaseClasses) *pFoundAllBaseClasses = foundAllBaseClasses; if (foundAllBaseClasses) //If we've seen all the base classes and none of the above were true then it must not be virtual return false; - return defaultVal; //If we can't see all the bases classes then we can't say conclusively + //If we can't see all the bases classes then we can't say conclusively, set inconclusive (if possible) and return default value + if (inconclusive) + *inconclusive = true; + return defaultVal; } std::vector Function::getOverloadedFunctions() const diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 954b31cc05b..36acb167315 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -783,7 +783,7 @@ class CPPCHECKLIB Function { void addArguments(const Scope *scope); /** @brief check if this function is virtual in the base classes */ - bool isImplicitlyVirtual(bool defaultVal = false, bool* pFoundAllBaseClasses = nullptr) const; + bool isImplicitlyVirtual(bool defaultVal = false, bool* pFoundAllBaseClasses = nullptr, bool* inconclusive = nullptr) const; std::vector getOverloadedFunctions() const; From 3a18138785ef565ce8db345efd6919fe7ced0dfb Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Thu, 19 Feb 2026 15:52:02 +0100 Subject: [PATCH 2/4] Fix #13944 tests --- test/testother.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/testother.cpp b/test/testother.cpp index 06512a930c9..a950a9f89cb 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -4676,6 +4676,24 @@ class TestOther : public TestFixture { ASSERT_EQUALS("[test.cpp:1:18]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:18]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); + + check("class A {\n" + "public:\n" + " void func01(QPoint* pt1) {\n" + " if (nullptr == pt1) {}\n" + " }\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:25]: (style) Parameter 'pt1' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); + + check("class B : public QObject {\n" + "public:\n" + " void func02(QPoint* pt2) {\n" + " if (nullptr == pt2) {}\n" + " }\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:25]: (style, inconclusive) Parameter 'pt2' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); } void constArray() { From 81ef7f1ff967229082fb8a08b612325c65534dcd Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Thu, 19 Feb 2026 16:44:36 +0100 Subject: [PATCH 3/4] fix --- lib/checkother.cpp | 4 ++-- lib/checkother.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 9f86296d0fe..3cab6f623a3 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1998,7 +1998,7 @@ void CheckOther::checkConstPointer() for (const Variable *p: pointers) { bool inconclusive = false; if (p->isArgument()) { - if (!p->scope() || !p->scope()->function || p->scope()->function->isImplicitlyVirtual(false, (bool*)nullptr, &inconclusive) || p->scope()->function->hasVirtualSpecifier()) + if (!p->scope() || !p->scope()->function || p->scope()->function->isImplicitlyVirtual(false, nullptr, &inconclusive) || p->scope()->function->hasVirtualSpecifier()) continue; if (p->isMaybeUnused()) continue; @@ -2020,7 +2020,7 @@ void CheckOther::checkConstPointer() } } -void CheckOther::constVariableError(const Variable *var, const Function *function, bool* inconclusive) +void CheckOther::constVariableError(const Variable *var, const Function *function, const bool *inconclusive) { if (!var) { reportError(nullptr, Severity::style, "constParameter", "Parameter 'x' can be declared with const"); diff --git a/lib/checkother.h b/lib/checkother.h index e8b6c7d1d75..bab08518857 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -215,7 +215,7 @@ class CPPCHECKLIB CheckOther : public Check { void suspiciousFloatingPointCastError(const Token *tok); void invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt); void passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor = false); - void constVariableError(const Variable *var, const Function *function, bool * inconclusive = nullptr); + void constVariableError(const Variable *var, const Function *function, const bool *inconclusive = nullptr); void constStatementError(const Token *tok, const std::string &type, bool inconclusive); void signedCharArrayIndexError(const Token *tok); void unknownSignCharArrayIndexError(const Token *tok); From f245c18fe0fee25006f7c33656aa744b8c2fe0f9 Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Fri, 20 Feb 2026 13:53:19 +0100 Subject: [PATCH 4/4] Updated according to findings Changed so that error message is in format "either ... or can be const" format. Added testcase with override, i.e. no warning. --- lib/checkother.cpp | 6 ++++-- test/testother.cpp | 13 ++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 3cab6f623a3..4e7231c4758 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -2039,7 +2039,9 @@ void CheckOther::constVariableError(const Variable *var, const Function *functio ErrorPath errorPath; std::string id = "const" + vartype; - std::string message = "$symbol:" + varname + "\n" + vartype + " '$symbol' can be declared as " + ptrRefArray; + std::string message = !(inconclusive && *inconclusive) ? + "$symbol:" + varname + "\n" + vartype + " '$symbol' can be declared as " + ptrRefArray : + "$symbol:" + varname + "\nEither there is missing override/final keyword, or the " + vartype + " '$symbol' can be " + ptrRefArray; errorPath.emplace_back(var->nameToken(), message); if (var->isArgument() && function && function->functionPointerUsage) { errorPath.emplace_front(function->functionPointerUsage, "You might need to cast the function pointer here"); @@ -2051,7 +2053,7 @@ void CheckOther::constVariableError(const Variable *var, const Function *functio id += "Pointer"; } - reportError(std::move(errorPath), Severity::style, id.c_str(), message, CWE398, (inconclusive && *inconclusive) ? Certainty::inconclusive : Certainty::normal); + reportError(std::move(errorPath), Severity::style, id.c_str(), message, CWE398, Certainty::normal); } //--------------------------------------------------------------------------- diff --git a/test/testother.cpp b/test/testother.cpp index a950a9f89cb..def57dda329 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -4686,14 +4686,21 @@ class TestOther : public TestFixture { ASSERT_EQUALS("[test.cpp:3:25]: (style) Parameter 'pt1' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("class B : public QObject {\n" + check("class A : public QObject {\n" "public:\n" - " void func02(QPoint* pt2) {\n" + " void func01(QPoint* pt2) {\n" " if (nullptr == pt2) {}\n" " }\n" "};\n"); - ASSERT_EQUALS("[test.cpp:3:25]: (style, inconclusive) Parameter 'pt2' can be declared as pointer to const [constParameterPointer]\n", + ASSERT_EQUALS("[test.cpp:3:25]: (style) Either there is missing override/final keyword, or the Parameter 'pt2' can be pointer to const [constParameterPointer]\n", errout_str()); + check("class A : public QObject {\n" + "public:\n" + " void func01(QPoint* pt3) override {\n" + " if (nullptr == pt3) {}\n" + " }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); } void constArray() {