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
88 changes: 48 additions & 40 deletions lib/checkother.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,53 +688,61 @@ void CheckOtherImpl::checkRedundantAssignment()
start = tok->astOperand2();
else
start = tok->findExpressionStartEndTokens().second->next();
const Token * tokenToCheck = tok->astOperand1();

// Check if we are working with union
for (const Token* tempToken = tokenToCheck; Token::simpleMatch(tempToken, ".");) {
tempToken = tempToken->astOperand1();
if (tempToken && tempToken->variable() && tempToken->variable()->type() && tempToken->variable()->type()->isUnionType())
tokenToCheck = tempToken;
}
std::vector<const Token*> tokensToCheck{ tok->astOperand1() };
if (Token::simpleMatch(tok->astOperand1(), "[") && Token::simpleMatch(tok->astOperand1()->astOperand1(), "auto")) // structured binding

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Possible FP with reference bindings: the isReference() bailout above (// todo: check references) runs on tok->astOperand1(), which is the [ token here and has no variable. So it never applies to the bound names. With auto& [x, y] = s; x = 1;, x is an alias for s.a and the write is observable, so it isn't redundant. The same applies to tuple-like types whose elements are references (e.g. auto [a, b] = std::tie(i, j);).

I'd suggest skipping elements whose variable()->isReference() is true (or bailing out when the declaration is auto&/auto&&), and adding a negative test for auto& [x, y] = s; x = 1; y = 2;.

tokensToCheck = astFlatten(tok->astOperand1()->astOperand2(), ",");

if (start->hasKnownSymbolicValue(tokenToCheck) && Token::simpleMatch(start->astParent(), "=") && !diag(tok)) {
const ValueFlow::Value* val = start->getKnownValue(ValueFlow::Value::ValueType::SYMBOLIC);
if (val->intvalue == 0) // no offset
redundantAssignmentSameValueError(tokenToCheck, val, tok->astOperand1()->expressionString());
}
for (const Token* tokenToCheck : tokensToCheck) {

// Get next assignment..
const Token *nextAssign = fwdAnalysis.reassign(tokenToCheck, start, scope->bodyEnd);
// extra check for union
if (nextAssign && tokenToCheck != tok->astOperand1()) {
nextAssign = fwdAnalysis.reassign(tok->astOperand1(), start, scope->bodyEnd);
// reading another member of the same union in the rhs is a use through aliasing
if (nextAssign && fwdAnalysis.hasOperand(nextAssign->astOperand2(), tokenToCheck))
nextAssign = nullptr;
}
// Check if we are working with union
for (const Token* tempToken = tokenToCheck; Token::simpleMatch(tempToken, ".");) {
tempToken = tempToken->astOperand1();
if (tempToken && tempToken->variable() && tempToken->variable()->type() && tempToken->variable()->type()->isUnionType())
tokenToCheck = tempToken;
}

if (!nextAssign)
continue;
if (start->hasKnownSymbolicValue(tokenToCheck) && Token::simpleMatch(start->astParent(), "=") && !diag(tok)) {
const ValueFlow::Value* val = start->getKnownValue(ValueFlow::Value::ValueType::SYMBOLIC);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Now that this runs inside the loop, there are two problems for structured bindings:

  • diag(tok) inserts tok the first time, so only the first bound element can ever get this diagnostic.
  • The message still uses tok->astOperand1()->expressionString(), which would print [x,y] instead of the element name. It should use exprTok/tokenToCheck, like the other diagnostics below.

if (val->intvalue == 0) // no offset
redundantAssignmentSameValueError(tokenToCheck, val, tok->astOperand1()->expressionString());
}

// there is redundant assignment. Is there a case between the assignments?
bool hasCase = false;
for (const Token *tok2 = tok; tok2 != nextAssign; tok2 = tok2->next()) {
if (tok2->str() == "break" || tok2->str() == "return")
break;
if (tok2->str() == "case") {
hasCase = true;
break;
// Get next assignment..
const Token* nextAssign = fwdAnalysis.reassign(tokenToCheck, start, scope->bodyEnd);
// extra check for union
const bool isUnion = tokenToCheck != tok->astOperand1() && !isStructuredBindingVariable(tokenToCheck->variable());
if (nextAssign && isUnion) {
nextAssign = fwdAnalysis.reassign(tok->astOperand1(), start, scope->bodyEnd);
// reading another member of the same union in the rhs is a use through aliasing
if (nextAssign && fwdAnalysis.hasOperand(nextAssign->astOperand2(), tokenToCheck))
nextAssign = nullptr;
}
}

// warn
if (hasCase)
redundantAssignmentInSwitchError(tok, nextAssign, tok->astOperand1()->expressionString());
else if (isInitialization)
redundantInitializationError(tok, nextAssign, tok->astOperand1()->expressionString(), inconclusive);
else {
diag(nextAssign);
redundantAssignmentError(tok, nextAssign, tok->astOperand1()->expressionString(), inconclusive);
if (!nextAssign)
continue;

// there is redundant assignment. Is there a case between the assignments?
bool hasCase = false;
for (const Token* tok2 = tok; tok2 != nextAssign; tok2 = tok2->next()) {
if (tok2->str() == "break" || tok2->str() == "return")
break;
if (tok2->str() == "case") {
hasCase = true;
break;
}
}

// warn
const Token* exprTok = isUnion ? tok->astOperand1() : tokenToCheck;
if (hasCase)
redundantAssignmentInSwitchError(tok, nextAssign, exprTok->expressionString());
else if (isInitialization)
redundantInitializationError(tok, nextAssign, exprTok->expressionString(), inconclusive);
else {
diag(nextAssign);
redundantAssignmentError(tok, nextAssign, exprTok->expressionString(), inconclusive);
}
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions test/testother.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11555,6 +11555,30 @@ class TestOther : public TestFixture {
"[test.cpp:8:10]: note: i is initialized\n"
"[test.cpp:9:7]: note: i is overwritten\n",
errout_str());

check("struct S { int a, b; };\n" // #15021
"int f(S s) {\n"
" auto [x, y] = s;\n"
" x = 1;\n"
" y = 2;\n"
" return x + y;\n"
"}\n"
"struct T { int c; };\n"
"int g(T t) {\n"
" auto [z] = t;\n"
" z = 0;\n"
" return z;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:4:7]: style: Variable 'x' is reassigned a value before the old one has been used. [redundantAssignment]\n"
"[test.cpp:3:17]: note: x is assigned\n"
"[test.cpp:4:7]: note: x is overwritten\n"
"[test.cpp:5:7]: style: Variable 'y' is reassigned a value before the old one has been used. [redundantAssignment]\n"
"[test.cpp:3:17]: note: y is assigned\n"
"[test.cpp:5:7]: note: y is overwritten\n"
"[test.cpp:11:7]: style: Variable 'z' is reassigned a value before the old one has been used. [redundantAssignment]\n"
"[test.cpp:10:14]: note: z is assigned\n"
"[test.cpp:11:7]: note: z is overwritten\n",
errout_str());
}

// cppcheck-suppress unusedPrivateFunction
Expand Down
Loading