Skip to content

Commit f198ea5

Browse files
committed
Fix false const-statement warnings for overloaded commas
1 parent 8bb772d commit f198ea5

2 files changed

Lines changed: 243 additions & 19 deletions

File tree

‎lib/checkother.cpp‎

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2300,7 +2300,81 @@ static bool isConstant(const Token* tok) {
23002300
return tok && (tok->isEnumerator() || Token::Match(tok, "%bool%|%num%|%str%|%char%|nullptr|NULL"));
23012301
}
23022302

2303-
static bool isConstStatement(const Token *tok, const Library& library, bool platformIndependent, bool isNestedBracket = false)
2303+
static bool isBuiltinCommaOperand(const Token* tok, const Settings& settings)
2304+
{
2305+
if (!tok)
2306+
return false;
2307+
const auto builtinType = [](const ValueType* vt) {
2308+
return vt && (vt->pointer > 0 || vt->type == ValueType::VOID || (vt->isPrimitive() && !vt->isEnum()));
2309+
};
2310+
// A comma chain can itself return an object through an overloaded operator.
2311+
if (tok->str() == ",")
2312+
return isBuiltinCommaOperand(tok->astOperand1(), settings) && isBuiltinCommaOperand(tok->astOperand2(), settings);
2313+
if (!tok->isAssignmentOp())
2314+
return builtinType(tok->valueType());
2315+
const ValueType* lhs = tok->astOperand1() ? tok->astOperand1()->valueType() : nullptr;
2316+
const ValueType* rhs = tok->astOperand2() ? tok->astOperand2()->valueType() : nullptr;
2317+
if (builtinType(lhs) && (tok->str() == "=" || builtinType(rhs)))
2318+
return true;
2319+
2320+
// Assignment value types are copied from the lhs, even when an overload
2321+
// returns another type. Do not select an arbitrary overload: only rely on
2322+
// the return type when all visible candidates have a built-in result.
2323+
for (const ValueType* vt : {lhs, rhs}) {
2324+
if (!builtinType(vt) && (!vt || !vt->typeScope))
2325+
return false;
2326+
if (vt && vt->typeScope) {
2327+
// Template aliases/qualifications and inherited overloads cannot
2328+
// be resolved reliably by comparing the available parameter types.
2329+
if (vt->typeScope->className.find('<') != std::string::npos ||
2330+
(vt->typeScope->definedType && !vt->typeScope->definedType->derivedFrom.empty()))
2331+
return false;
2332+
}
2333+
}
2334+
if (!tok->scope())
2335+
return false;
2336+
bool found = false;
2337+
const std::string name = "operator" + tok->str();
2338+
// Include non-member candidates conservatively, including hidden friends
2339+
// and anonymous namespaces, without trying to rank overloads.
2340+
for (const Scope& scope : tok->scope()->symdb.scopeList) {
2341+
const auto range = scope.functionMap.equal_range(name);
2342+
for (auto it = range.first; it != range.second; ++it) {
2343+
const Function* function = it->second;
2344+
const bool member = scope.isClassOrStruct() && !function->isFriend();
2345+
if (member && (!lhs || lhs->typeScope != &scope))
2346+
continue;
2347+
if (function->argCount() != (member ? 1U : 2U))
2348+
continue;
2349+
bool matches = true;
2350+
for (unsigned int arg = 0; arg < function->argCount(); ++arg) {
2351+
const ValueType* operand = member || arg == 1U ? rhs : lhs;
2352+
const ValueType* parameter = function->getArgumentVar(arg)->valueType();
2353+
const auto match = ValueType::matchParameter(operand, parameter);
2354+
if (match == ValueType::MatchResult::UNKNOWN)
2355+
return false;
2356+
if (match == ValueType::MatchResult::NOMATCH) {
2357+
// A user-defined conversion may still make this candidate
2358+
// viable; matchParameter does not resolve those conversions.
2359+
if (!builtinType(operand) || !builtinType(parameter))
2360+
return false;
2361+
matches = false;
2362+
}
2363+
}
2364+
if (!matches)
2365+
continue;
2366+
if (!function->retDef)
2367+
return false;
2368+
const ValueType result = ValueType::parseDecl(function->retDef, settings);
2369+
if (!builtinType(&result))
2370+
return false;
2371+
found = true;
2372+
}
2373+
}
2374+
return found;
2375+
}
2376+
2377+
static bool isConstStatement(const Token *tok, const Settings& settings, bool platformIndependent, bool isNestedBracket = false)
23042378
{
23052379
if (!tok)
23062380
return false;
@@ -2322,7 +2396,7 @@ static bool isConstStatement(const Token *tok, const Library& library, bool plat
23222396
tok2 = tok2->astParent();
23232397
}
23242398
if (Token::Match(tok, "&&|%oror%"))
2325-
return isConstStatement(tok->astOperand1(), library, platformIndependent) && isConstStatement(tok->astOperand2(), library, platformIndependent);
2399+
return isConstStatement(tok->astOperand1(), settings, platformIndependent) && isConstStatement(tok->astOperand2(), settings, platformIndependent);
23262400
if (Token::Match(tok, "!|~|%cop%") && (tok->astOperand1() || tok->astOperand2()))
23272401
return true;
23282402
if (Token::simpleMatch(tok->previous(), "sizeof ("))
@@ -2332,38 +2406,40 @@ static bool isConstStatement(const Token *tok, const Library& library, bool plat
23322406
if (isCPPCast(tok)) {
23332407
if (Token::simpleMatch(tok->astOperand1(), "dynamic_cast") && Token::simpleMatch(tok->astOperand1()->linkAt(1)->previous(), "& >"))
23342408
return false;
2335-
return isWithoutSideEffects(tok) && isConstStatement(tok->astOperand2(), library, platformIndependent);
2409+
return isWithoutSideEffects(tok) && isConstStatement(tok->astOperand2(), settings, platformIndependent);
23362410
}
23372411
if (tok->isCast() && tok->next() && tok->next()->isStandardType())
2338-
return isWithoutSideEffects(tok->astOperand1()) && isConstStatement(tok->astOperand1(), library, platformIndependent);
2412+
return isWithoutSideEffects(tok->astOperand1()) && isConstStatement(tok->astOperand1(), settings, platformIndependent);
23392413
if (Token::simpleMatch(tok, "."))
2340-
return isConstStatement(tok->astOperand2(), library, platformIndependent);
2414+
return isConstStatement(tok->astOperand2(), settings, platformIndependent);
23412415
if (Token::simpleMatch(tok, ",")) {
2416+
if (tok->isCpp() && (!isBuiltinCommaOperand(tok->astOperand1(), settings) || !isBuiltinCommaOperand(tok->astOperand2(), settings)))
2417+
return false;
23422418
if (tok->astParent()) // warn about const statement on rhs at the top level
2343-
return isConstStatement(tok->astOperand1(), library, platformIndependent) &&
2344-
isConstStatement(tok->astOperand2(), library, platformIndependent);
2419+
return isConstStatement(tok->astOperand1(), settings, platformIndependent) &&
2420+
isConstStatement(tok->astOperand2(), settings, platformIndependent);
23452421

23462422
const Token* lml = previousBeforeAstLeftmostLeaf(tok); // don't warn about matrix/vector assignment (e.g. Eigen)
23472423
if (lml)
23482424
lml = lml->next();
23492425
const Token* stream = lml;
23502426
while (stream && Token::Match(stream->astParent(), ".|[|(|*"))
23512427
stream = stream->astParent();
2352-
return (!stream || !isLikelyStream(stream)) && isConstStatement(tok->astOperand2(), library, platformIndependent);
2428+
return (!stream || !isLikelyStream(stream)) && isConstStatement(tok->astOperand2(), settings, platformIndependent);
23532429
}
23542430
if (Token::simpleMatch(tok, "?") && Token::simpleMatch(tok->astOperand2(), ":")) // ternary operator
2355-
return isConstStatement(tok->astOperand1(), library, platformIndependent) &&
2356-
isConstStatement(tok->astOperand2()->astOperand1(), library, platformIndependent) &&
2357-
isConstStatement(tok->astOperand2()->astOperand2(), library, platformIndependent);
2431+
return isConstStatement(tok->astOperand1(), settings, platformIndependent) &&
2432+
isConstStatement(tok->astOperand2()->astOperand1(), settings, platformIndependent) &&
2433+
isConstStatement(tok->astOperand2()->astOperand2(), settings, platformIndependent);
23582434
if (isBracketAccess(tok) && isWithoutSideEffects(tok->astOperand1(), /*checkArrayAccess*/ true, /*checkReference*/ false)) {
23592435
const bool isChained = succeeds(tok->astParent(), tok);
23602436
if (Token::simpleMatch(tok->astParent(), "[")) {
23612437
if (isChained)
2362-
return isConstStatement(tok->astOperand2(), library, platformIndependent) &&
2363-
isConstStatement(tok->astParent(), library, platformIndependent);
2364-
return isNestedBracket && isConstStatement(tok->astOperand2(), library, platformIndependent);
2438+
return isConstStatement(tok->astOperand2(), settings, platformIndependent) &&
2439+
isConstStatement(tok->astParent(), settings, platformIndependent);
2440+
return isNestedBracket && isConstStatement(tok->astOperand2(), settings, platformIndependent);
23652441
}
2366-
return isConstStatement(tok->astOperand2(), library, platformIndependent, /*isNestedBracket*/ !isChained);
2442+
return isConstStatement(tok->astOperand2(), settings, platformIndependent, /*isNestedBracket*/ !isChained);
23672443
}
23682444
if (!tok->astParent() && findLambdaEndToken(tok))
23692445
return true;
@@ -2379,7 +2455,7 @@ static bool isConstStatement(const Token *tok, const Library& library, bool plat
23792455
funcStr.insert(0, tok2->strAt(-2) + "::");
23802456
tok2 = tok2->tokAt(-2);
23812457
}
2382-
if (library.functions().count(funcStr) > 0)
2458+
if (settings.library.functions().count(funcStr) > 0)
23832459
return true;
23842460
}
23852461
return false;
@@ -2468,7 +2544,7 @@ void CheckOtherImpl::checkIncompleteStatement()
24682544
// Skip statement expressions
24692545
if (Token::simpleMatch(rtok, "; } )") || Token::simpleMatch(tok->next(), "; } )"))
24702546
continue;
2471-
if (!isConstStatement(tok, mSettings.library, false))
2547+
if (!isConstStatement(tok, mSettings, false))
24722548
continue;
24732549
if (isVoidStmt(tok))
24742550
continue;
@@ -2657,7 +2733,7 @@ void CheckOtherImpl::checkMisusedScopedObject()
26572733
if (Token::simpleMatch(parTok, "<") && parTok->link())
26582734
parTok = parTok->link()->next();
26592735
if (const Token* arg = parTok->astOperand2()) {
2660-
if (!isConstStatement(arg, mSettings.library, false))
2736+
if (!isConstStatement(arg, mSettings, false))
26612737
continue;
26622738
if (parTok->str() == "(") {
26632739
if (arg->varId() && !(arg->variable() && arg->variable()->nameToken() != arg))
@@ -3138,7 +3214,7 @@ void CheckOtherImpl::checkDuplicateExpression()
31383214

31393215
else if (!tok->astOperand1()->values().empty() && !tok->astOperand2()->values().empty() && isEqualKnownValue(tok->astOperand1(), tok->astOperand2()) &&
31403216
!isVariableChanged(tok->astParent(), /*indirect*/ 0, mSettings) &&
3141-
isConstStatement(tok->astOperand1(), mSettings.library, true) && isConstStatement(tok->astOperand2(), mSettings.library, true))
3217+
isConstStatement(tok->astOperand1(), mSettings, true) && isConstStatement(tok->astOperand2(), mSettings, true))
31423218
duplicateValueTernaryError(tok);
31433219
}
31443220
}

‎test/testincompletestatement.cpp‎

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ class TestIncompleteStatement : public TestFixture {
8080
TEST_CASE(mapindex);
8181
TEST_CASE(commaoperator1);
8282
TEST_CASE(commaoperator2);
83+
TEST_CASE(commaoperatorOverloaded);
84+
TEST_CASE(commaoperatorBuiltin);
85+
TEST_CASE(commaoperatorOverloadCandidates);
8386
TEST_CASE(redundantstmts);
8487
TEST_CASE(vardecl);
8588
TEST_CASE(archive); // ar & x
@@ -434,6 +437,151 @@ class TestIncompleteStatement : public TestFixture {
434437
ASSERT_EQUALS("", errout_str());
435438
}
436439

440+
void commaoperatorOverloaded() { // #4651
441+
check("void f() {\n"
442+
" using namespace boost::assign;\n"
443+
" std::vector<int> values;\n"
444+
" values += 2, 2;\n"
445+
"}\n");
446+
ASSERT_EQUALS("", errout_str());
447+
448+
check("struct Collector {\n"
449+
" Collector& operator+=(int);\n"
450+
" Collector& operator,(int);\n"
451+
"};\n"
452+
"void f(Collector& values) {\n"
453+
" values += 1, 2, 3;\n"
454+
"}\n");
455+
ASSERT_EQUALS("", errout_str());
456+
457+
check("struct Collector {};\n"
458+
"Collector& operator+=(Collector&, int);\n"
459+
"Collector& operator,(Collector&, int);\n"
460+
"void f(Collector& values) {\n"
461+
" values += 1, 2;\n"
462+
"}\n");
463+
ASSERT_EQUALS("", errout_str());
464+
465+
check("void f(Unknown& values) {\n"
466+
" values += 1, 2;\n"
467+
"}\n");
468+
ASSERT_EQUALS("", errout_str());
469+
470+
check("struct Collector { void operator,(int); };\n"
471+
"void f(Collector& values) {\n"
472+
" values, 2;\n"
473+
"}\n");
474+
ASSERT_EQUALS("", errout_str());
475+
476+
check("enum Value { First };\n"
477+
"void operator,(Value, int);\n"
478+
"void f(Value value) {\n"
479+
" value, 2;\n"
480+
"}\n");
481+
ASSERT_EQUALS("", errout_str());
482+
}
483+
484+
void commaoperatorBuiltin() {
485+
check("void f(int& value) {\n"
486+
" value += 1, 2;\n"
487+
"}\n");
488+
ASSERT_EQUALS("[test.cpp:2:15]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
489+
490+
check("struct S { operator int() const; };\n"
491+
"void f(S value) {\n"
492+
" int i;\n"
493+
" i = value, 2;\n"
494+
"}\n");
495+
ASSERT_EQUALS("[test.cpp:4:14]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
496+
497+
check("void f(int*& value) {\n"
498+
" value = nullptr, 2;\n"
499+
"}\n");
500+
ASSERT_EQUALS("[test.cpp:2:20]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
501+
502+
check("void f(int* value) {\n"
503+
" value += 1, 2;\n"
504+
"}\n", dinit(CheckOptions, $.cpp = false));
505+
ASSERT_EQUALS("[test.c:2:15]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
506+
507+
check("struct Collector { int operator+=(int); };\n"
508+
"void f(Collector& values) {\n"
509+
" values += 1, 2;\n"
510+
"}\n");
511+
ASSERT_EQUALS("[test.cpp:3:16]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
512+
513+
check("struct Collector { void operator+=(int); };\n"
514+
"void f(Collector& values) {\n"
515+
" values += 1, 2;\n"
516+
"}\n");
517+
ASSERT_EQUALS("[test.cpp:3:16]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
518+
519+
check("struct Collector {};\n"
520+
"int operator+=(Collector&, int);\n"
521+
"void f(Collector& values) {\n"
522+
" values += 1, 2;\n"
523+
"}\n");
524+
ASSERT_EQUALS("[test.cpp:4:16]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
525+
526+
check("struct Collector { void operator,(int); };\n"
527+
"void f(Collector& values) {\n"
528+
" (void)values, 2;\n"
529+
"}\n");
530+
ASSERT_EQUALS("[test.cpp:3:17]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
531+
532+
check("struct Collector { Collector* operator+=(int); };\n"
533+
"void f(Collector& values) {\n"
534+
" values += 1, 2;\n"
535+
"}\n");
536+
ASSERT_EQUALS("[test.cpp:3:16]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str());
537+
}
538+
539+
void commaoperatorOverloadCandidates() {
540+
check("struct Collector { int operator+=(double); };\n"
541+
"namespace {\n"
542+
" Collector& operator+=(Collector&, int);\n"
543+
" Collector& operator,(Collector&, int);\n"
544+
"}\n"
545+
"void f(Collector& values) { values += 1, 2; }\n");
546+
ASSERT_EQUALS("", errout_str());
547+
548+
check("struct Collector {\n"
549+
" int operator+=(double);\n"
550+
" Collector& operator+=(int);\n"
551+
" void operator,(int);\n"
552+
"};\n"
553+
"void f(Collector& values) { values += 1, 2; }\n");
554+
ASSERT_EQUALS("", errout_str());
555+
556+
check("struct Collector {\n"
557+
" Collector& operator+=(int);\n"
558+
" int operator+=(double);\n"
559+
" void operator,(int);\n"
560+
"};\n"
561+
"void f(Collector& values) { values += 1, 2; }\n");
562+
ASSERT_EQUALS("", errout_str());
563+
564+
check("namespace N { struct Tag {}; }\n"
565+
"template<class T> struct Box { int operator+=(double); };\n"
566+
"namespace N {\n"
567+
" Box<Tag>& operator+=(Box<Tag>&, int);\n"
568+
" Box<Tag>& operator,(Box<Tag>&, int);\n"
569+
"}\n"
570+
"void f(Box<N::Tag>& values) { values += 1, 2; }\n");
571+
ASSERT_EQUALS("", errout_str());
572+
573+
check("struct B {};\n"
574+
"struct A { operator B() const; };\n"
575+
"struct Collector {\n"
576+
" int operator+=(A) &&;\n"
577+
" Collector& operator+=(B) &;\n"
578+
" void operator,(int);\n"
579+
"};\n"
580+
"void f(Collector& values, A a) { values += a, 2; }\n");
581+
ASSERT_EQUALS("", errout_str());
582+
583+
}
584+
437585
// #8451
438586
void redundantstmts() {
439587
check("void f1(int x) {\n"

0 commit comments

Comments
 (0)