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
27 changes: 27 additions & 0 deletions lib/astutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2715,6 +2715,33 @@ 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 Scope* lhsScope = lhsType->typeScope;
const auto operators = lhsScope->functionMap.equal_range("operator>>=");
if (operators.first == operators.second ||
(lhsScope->definedType && !lhsScope->definedType->derivedFrom.empty()) ||
Token::findsimplematch(lhsScope->bodyStart, "friend", lhsScope->bodyEnd))
return true;
// Non-member operators can compete with members. Avoid ranking them.
for (const Scope& scope : lhsScope->symdb().scopeList) {
const auto functions = scope.functionMap.equal_range("operator>>=");
for (auto it = functions.first; it != functions.second; ++it) {
if (!scope.isClassOrStruct() || it->second->isFriend())
return true;
}
}
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;

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

struct CheckOptions
Expand Down Expand Up @@ -6756,6 +6758,113 @@ 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"
" if (x < 0) return true;\n"
" return false;\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"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:5:11]: (style) Condition '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"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:5:11]: (style) Condition 'x<0' is always false [knownConditionTrueFalse]\n", errout_str());

check("bool f(int bits) {\n"
" int shift = 1;\n"
" bits >>= shift;\n"
" if (shift < 0) return true;\n"
" return bits == 0;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:4:15]: (style) Condition 'shift<0' is always false [knownConditionTrueFalse]\n", errout_str());
}

void knownConditionShiftAssignmentFallback() { // #6552
// Without overload resolution, assume the RHS may be changed.
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"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("namespace CORBA { typedef int Long; struct Any {}; }\n"
"bool operator>>=(const CORBA::Any&, CORBA::Long&);\n"
"bool f(const CORBA::Any& a) {\n"
" CORBA::Long x = 1;\n"
" a >>= x;\n"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("struct Base { void operator>>=(int&) const; };\n"
"struct Derived : Base {};\n"
"bool f(const Derived& d) {\n"
" int x = 1;\n"
" d >>= x;\n"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("struct Value {};\n"
"bool f(const Value& value) {\n"
" double x = 0.5;\n"
" value >>= x;\n"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("struct Base { void operator>>=(double&) const; };\n"
"struct Derived : Base { using Base::operator>>=; void operator>>=(int) const; };\n"
"bool f(const Derived& value) {\n"
" double x = 0.5;\n"
" value >>= x;\n"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("struct Value { void operator>>=(double) const; };\n"
"void operator>>=(Value&, double&);\n"
"bool f(Value& value) {\n"
" double x = 0.5;\n"
" value >>= x;\n"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("struct Value { void operator>>=(double) const; friend void operator>>=(Value&, double&); };\n"
"bool f(Value& value) {\n"
" double x = 0.5;\n"
" value >>= x;\n"
" if (x < 0) return true;\n"
" return false;\n"
"}\n");
ASSERT_EQUALS("", errout_str());
}

void knownConditionFloating() {
check("void foo() {\n" // #11199
" float f = 1.0;\n"
Expand Down