From 8cf85df574190973927d6571315f8c2ad94afad1 Mon Sep 17 00:00:00 2001 From: tzi4 Date: Tue, 8 Sep 2026 15:35:19 +0300 Subject: [PATCH 1/2] Fix #6552: false constant value warnings after overloaded extraction --- lib/astutils.cpp | 14 ++++++++++++++ test/testastutils.cpp | 4 ++++ test/testcondition.cpp | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 1faafd4ba09..345868c8cd6 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -2712,6 +2712,20 @@ bool isVariableChanged(const Token *tok, int indirect, const Settings &settings, if (tok2->isCpp() && Token::Match(tok2->astParent(), ">>|&") && astIsRHS(tok2) && isLikelyStreamRead(tok2->astParent())) return true; + // An overloaded >>= can extract into its right-hand operand by reference. + if (tok2->isCpp() && Token::simpleMatch(tok2->astParent(), ">>=") && astIsRHS(tok2) && + !astIsIntegral(tok2->astParent()->astOperand1(), false)) { + const ValueType* lhsType = tok2->astParent()->astOperand1()->valueType(); + if (!lhsType || !lhsType->typeScope) + return true; + const auto operators = lhsType->typeScope->functionMap.equal_range("operator>>="); + for (auto it = operators.first; it != operators.second; ++it) { + const Variable* arg = it->second->getArgumentVar(0); + if (!arg || (!arg->isConst() && arg->isReference())) + return true; + } + } + if (isLikelyStream(tok2)) return true; diff --git a/test/testastutils.cpp b/test/testastutils.cpp index 46dde522338..8061cd2e55e 100644 --- a/test/testastutils.cpp +++ b/test/testastutils.cpp @@ -229,6 +229,10 @@ class TestAstUtils : public TestFixture { } void isVariableChangedTest() { + // Built-in shifts do not modify their right-hand operand. #6552 + ASSERT_EQUALS(false, isVariableChanged("void f(int shift, int bits) { bits >>= shift; }\n", "{", "}")); + ASSERT_EQUALS(false, isVariableChanged("void f(const double x, const Value& value) { value >>= x; }\n", "{", "}")); + ASSERT_EQUALS(true, isVariableChanged("void f(double x, const Value& value) { value >>= x; }\n", "{", "}")); // #8211 - no lhs for >> , do not crash (void)isVariableChanged("void f() {\n" " int b;\n" diff --git a/test/testcondition.cpp b/test/testcondition.cpp index 5d57ad3e312..4d759096d1f 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -129,6 +129,7 @@ class TestCondition : public TestFixture { TEST_CASE(knownConditionAfterBailout); // #12526 TEST_CASE(knownConditionIncDecOperator); TEST_CASE(knownConditionFloating); + TEST_CASE(knownConditionShiftAssignment); } struct CheckOptions @@ -6687,6 +6688,40 @@ class TestCondition : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void knownConditionShiftAssignment() { // #6552 + check("struct Value { void operator>>=(double&) const; };\n" + "bool f(const Value& value, bool extract) {\n" + " double x = 0.5;\n" + " if (extract) value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Value { void operator>>=(double) const; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(const double&) const; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(const double&) const; };\n" + "bool f(const Value& value) {\n" + " const double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + } + void knownConditionFloating() { check("void foo() {\n" // #11199 " float f = 1.0;\n" From 3eecb970fbb3ecf03121ce4e9509cd3da520f1ad Mon Sep 17 00:00:00 2001 From: tzi4 Date: Tue, 8 Sep 2026 16:51:34 +0300 Subject: [PATCH 2/2] Preserve value tracking across non-mutating extraction overloads --- lib/astutils.cpp | 66 +++++++++++++- test/testcondition.cpp | 199 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+), 3 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 345868c8cd6..31b9db851a9 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -2715,14 +2715,74 @@ bool isVariableChanged(const Token *tok, int indirect, const Settings &settings, // An overloaded >>= can extract into its right-hand operand by reference. if (tok2->isCpp() && Token::simpleMatch(tok2->astParent(), ">>=") && astIsRHS(tok2) && !astIsIntegral(tok2->astParent()->astOperand1(), false)) { - const ValueType* lhsType = tok2->astParent()->astOperand1()->valueType(); + const Token* lhs = tok2->astParent()->astOperand1(); + const ValueType* lhsType = lhs->valueType(); if (!lhsType || !lhsType->typeScope) return true; + const ValueType* rhsType = tok2->valueType(); + const auto isKnownType = [](const ValueType* type) { + return type && type->type != ValueType::UNKNOWN_INT && + (type->isPrimitive() || (type->pointer && (type->type == ValueType::VOID || type->typeScope))); + }; + const auto receiverCV = [](const Function* function) { + return (function->isConst() ? 1U : 0U) | (function->isVolatile() ? 2U : 0U); + }; + const unsigned int lhsCV = (lhsType->isConst() ? 1U : 0U) | (lhsType->isVolatile() ? 2U : 0U); const auto operators = lhsType->typeScope->functionMap.equal_range("operator>>="); for (auto it = operators.first; it != operators.second; ++it) { - const Variable* arg = it->second->getArgumentVar(0); - if (!arg || (!arg->isConst() && arg->isReference())) + const Function* function = it->second; + if ((lhsType->isConst() && !function->isConst()) || + (lhsType->isVolatile() && !function->isVolatile()) || + (lhs->variable() && function->hasRvalRefQualifier())) + continue; + const Variable* arg = function->getArgumentVar(0); + if (!arg) return true; + if (arg->isConst() || !arg->isReference()) + continue; + // Named variables are lvalues, including named rvalue references. + if (tok2->variable() && arg->isRValueReference() && !function->templateDef) + continue; + const ValueType* argType = arg->valueType(); + if (isKnownType(rhsType) && isKnownType(argType)) { + // Non-const references cannot bind via arithmetic or pointer conversions. + if (!rhsType->isTypeEqual(argType) || + (rhsType->sign != ValueType::UNKNOWN_SIGN && argType->sign != ValueType::UNKNOWN_SIGN && + rhsType->sign != argType->sign) || + (rhsType->isVolatile() && !argType->isVolatile())) + continue; + // Pointee qualification conversions also create a temporary pointer. + if (rhsType->pointer > 0 && rhsType->pointer < std::numeric_limits::digits) { + const unsigned int mask = (1U << rhsType->pointer) - 1; + if (((rhsType->constness ^ argType->constness) & mask) || + ((rhsType->volatileness ^ argType->volatileness) & mask)) + continue; + } + } + // An exact by-value argument can win on the receiver's cv conversion. + // Do not rank user-defined conversions or competing reference bindings. + if (lhs->variable() && isKnownType(rhsType) && isKnownType(argType) && + rhsType->isTypeEqual(argType) && rhsType->sign == argType->sign && + rhsType->constness == argType->constness && rhsType->volatileness == argType->volatileness && + !function->templateDef) { + const unsigned int cv = receiverCV(function); + const bool hasBetterValueOverload = std::any_of(operators.first, operators.second, [&](const std::pair& entry) { + const Function* other = entry.second; + const unsigned int otherCV = receiverCV(other); + if (otherCV == cv || (otherCV & cv) != otherCV || (otherCV & lhsCV) != lhsCV || + other->hasLvalRefQualifier() != function->hasLvalRefQualifier() || + other->hasRvalRefQualifier() != function->hasRvalRefQualifier() || other->templateDef) + return false; + const Variable* otherArg = other->getArgumentVar(0); + const ValueType* otherType = otherArg ? otherArg->valueType() : nullptr; + return otherArg && !otherArg->isReference() && isKnownType(otherType) && + rhsType->isTypeEqual(otherType) && rhsType->sign == otherType->sign && + rhsType->constness == otherType->constness && rhsType->volatileness == otherType->volatileness; + }); + if (hasBetterValueOverload) + continue; + } + return true; } } diff --git a/test/testcondition.cpp b/test/testcondition.cpp index 4d759096d1f..c3663edbe1c 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -130,6 +130,7 @@ class TestCondition : public TestFixture { TEST_CASE(knownConditionIncDecOperator); TEST_CASE(knownConditionFloating); TEST_CASE(knownConditionShiftAssignment); + TEST_CASE(knownConditionShiftAssignmentOverloads); } struct CheckOptions @@ -6722,6 +6723,204 @@ class TestCondition : public TestFixture { ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); } + void knownConditionShiftAssignmentOverloads() { + check("struct Value { void operator>>=(double) const; void operator>>=(int&) const; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double) const; void operator>>=(float&) const; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(int) const; void operator>>=(unsigned int&) const; };\n" + "bool f(const Value& value) {\n" + " int x = 1;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("enum E { A };\n" + "struct Value { void operator>>=(int) const; void operator>>=(E&) const; };\n" + "bool f(const Value& value) {\n" + " int x = 1;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double) const; void operator>>=(double&); };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double) volatile; void operator>>=(double&); };\n" + "bool f(volatile Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double) const &; void operator>>=(double&) &&; };\n" + "bool f(Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(const double&) const; void operator>>=(double&&) const; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(int) const; void operator>>=(double&) const; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Value { void operator>>=(int) const; void operator>>=(volatile double&) const; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Value { void operator>>=(double) const; void operator>>=(double&); };\n" + "bool f(Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Value { template void operator>>=(T&& x) const { x = -1; } };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Value { void operator>>=(double*) const; void operator>>=(int*&) const; };\n" + "bool f(const Value& value) {\n" + " double* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x==nullptr' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double*) const; void operator>>=(void*&) const; };\n" + "bool f(const Value& value) {\n" + " double* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x==nullptr' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double*) const; void operator>>=(const double*&) const; };\n" + "bool f(const Value& value) {\n" + " double* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x==nullptr' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double*) const; void operator>>=(double**&) const; };\n" + "bool f(const Value& value) {\n" + " double* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x==nullptr' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(int*) const; void operator>>=(double*&) const; };\n" + "bool f(const Value& value) {\n" + " double* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Value { void operator>>=(int*) const; void operator>>=(const double*&) const; };\n" + "bool f(const Value& value) {\n" + " const double* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct A {}; struct B {};\n" + "struct Value { void operator>>=(A*) const; void operator>>=(B*&) const; };\n" + "bool f(const Value& value) {\n" + " A* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:14]: (style) Return value 'x==nullptr' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("struct Base {}; struct A : Base {};\n" + "struct Value { void operator>>=(A*) const; void operator>>=(Base*&) const; };\n" + "bool f(const Value& value) {\n" + " A* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:14]: (style) Return value 'x==nullptr' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("struct A {}; struct B {};\n" + "struct Value { void operator>>=(B*) const; void operator>>=(A*&) const; };\n" + "bool f(const Value& value) {\n" + " A* x = nullptr;\n" + " value >>= x;\n" + " return x == nullptr;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Value { void operator>>=(double) ; void operator>>=(double&) const; };\n" + "bool f(Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double) const; void operator>>=(double&) const volatile; };\n" + "bool f(const Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + + check("struct Value { void operator>>=(double) volatile; void operator>>=(double&) const volatile; };\n" + "bool f(volatile Value& value) {\n" + " double x = 0.5;\n" + " value >>= x;\n" + " return x < 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (style) Return value 'x<0' is always false [knownConditionTrueFalse]\n", errout_str()); + } + void knownConditionFloating() { check("void foo() {\n" // #11199 " float f = 1.0;\n"