Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions lib/astutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2712,6 +2712,80 @@ 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 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>>=");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an AI review take it with a grain of salt. Please feel free to reject it by clicking on Resolve button

Only member operators of the lhs class itself are looked at. When none is found, the loop does nothing and we fall through, so x is still treated as unchanged. I built this branch and the false positive remains for:

// non-member operator, as in CORBA::Any (the classic `any >>= x` use case)
namespace CORBA { typedef int Long; struct Any {}; }
bool operator>>=(const CORBA::Any&, CORBA::Long&);
bool f1(const CORBA::Any& a) {
    CORBA::Long x = 1;
    a >>= x;
    if (x < 0) return true;   // knownConditionTrueFalse
    return false;
}

// operator inherited from a base class
struct Base { void operator>>=(int&) const; };
struct Derived : Base {};
bool f2(const Derived& d) {
    int x = 1;
    d >>= x;
    if (x < 0) return true;   // knownConditionTrueFalse
    return false;
}

Since an FN is better than an FP here, a simpler and more robust rule might be: for a class-type lhs, assume the rhs is changed unless a member operator>>= is found and all the candidates take the argument by value or by const reference. That would also make most of the overload-ranking logic below unnecessary. It's quite a lot of code to keep warnings in rare cases, and it is hard to be sure it matches C++ overload resolution.

for (auto it = operators.first; it != operators.second; ++it) {
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<unsigned int>::digits) {
const unsigned int mask = (1U << rhsType->pointer) - 1;
if (((static_cast<unsigned int>(rhsType->constness) ^ static_cast<unsigned int>(argType->constness)) & mask) ||
((static_cast<unsigned int>(rhsType->volatileness) ^ static_cast<unsigned int>(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<const std::string, const Function*>& 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;
}
}

if (isLikelyStream(tok2))
return true;

Expand Down
4 changes: 4 additions & 0 deletions test/testastutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
234 changes: 234 additions & 0 deletions test/testcondition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ class TestCondition : public TestFixture {
TEST_CASE(knownConditionAfterBailout); // #12526
TEST_CASE(knownConditionIncDecOperator);
TEST_CASE(knownConditionFloating);
TEST_CASE(knownConditionShiftAssignment);
TEST_CASE(knownConditionShiftAssignmentOverloads);
}

struct CheckOptions
Expand Down Expand Up @@ -6687,6 +6689,238 @@ class TestCondition : public TestFixture {
ASSERT_EQUALS("", errout_str());
}

void knownConditionShiftAssignment() { // #6552

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an AI review take it with a grain of salt. Please feel free to reject it by clicking on Resolve button

FYI: since f99ea4b on main (#15056, "remove warnings for return result"), knownConditionTrueFalse is no longer reported for return x < 0;. I checked with current main: even int x = 1; return x < 0; gives no warning. After rebasing, the tests here that expect Return value 'x<0' is always false will fail. Worse, the tests that expect "" will pass whether or not the fix works. Using if (x < 0) {} instead of return x < 0; in these tests should keep them meaningful.

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 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<class T> 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"
Expand Down
Loading