Fix 10129: false negative: knownConditionTrueFalse (#3382)

* Add symbolic matching

* Check for truncated values

* Dont propagate uninit values

* Update errorpath test

* Add test case for 10129

* Add test case for FP

* Remove symbolic values that are the same as the token

* Fix test messages

* Fix cppcheck issue

* Format
This commit is contained in:
Paul Fultz II 2021-08-04 14:07:31 -05:00 committed by GitHub
parent e6cc7201b0
commit 8b8ae55490
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 158 additions and 28 deletions

View File

@ -43,6 +43,7 @@ struct Analyzer {
Match = (1 << 4), Match = (1 << 4),
Idempotent = (1 << 5), Idempotent = (1 << 5),
Incremental = (1 << 6), Incremental = (1 << 6),
SymbolicMatch = (1 << 7),
}; };
void set(unsigned int f, bool state = true) { void set(unsigned int f, bool state = true) {
@ -85,6 +86,8 @@ struct Analyzer {
return get(Incremental); return get(Incremental);
} }
bool isSymbolicMatch() const { return get(SymbolicMatch); }
bool matches() const { bool matches() const {
return get(Match); return get(Match);
} }

View File

@ -375,14 +375,16 @@ static T* nextAfterAstRightmostLeafGeneric(T* tok)
rightmostLeaf = lam; rightmostLeaf = lam;
break; break;
} }
if (rightmostLeaf->astOperand2()) if (rightmostLeaf->astOperand2() && precedes(rightmostLeaf, rightmostLeaf->astOperand2()))
rightmostLeaf = rightmostLeaf->astOperand2(); rightmostLeaf = rightmostLeaf->astOperand2();
else else if (rightmostLeaf->astOperand1() && precedes(rightmostLeaf, rightmostLeaf->astOperand1()))
rightmostLeaf = rightmostLeaf->astOperand1(); rightmostLeaf = rightmostLeaf->astOperand1();
} while (rightmostLeaf->astOperand1()); else
break;
} while (rightmostLeaf->astOperand1() || rightmostLeaf->astOperand2());
while (Token::Match(rightmostLeaf->next(), "]|)") && !hasToken(rightmostLeaf->next()->link(), rightmostLeaf->next(), tok)) while (Token::Match(rightmostLeaf->next(), "]|)") && !hasToken(rightmostLeaf->next()->link(), rightmostLeaf->next(), tok))
rightmostLeaf = rightmostLeaf->next(); rightmostLeaf = rightmostLeaf->next();
if (rightmostLeaf->str() == "{" && rightmostLeaf->link()) if (Token::Match(rightmostLeaf, "{|(|[") && rightmostLeaf->link())
rightmostLeaf = rightmostLeaf->link(); rightmostLeaf = rightmostLeaf->link();
return rightmostLeaf->next(); return rightmostLeaf->next();
} }

View File

@ -2107,6 +2107,28 @@ struct ValueFlowAnalyzer : Analyzer {
} }
} }
virtual bool useSymbolicValues() const { return true; }
bool isSameSymbolicValue(const Token* tok, ErrorPath* errorPath = nullptr) const
{
if (!useSymbolicValues())
return false;
for (const ValueFlow::Value& v : tok->values()) {
if (!v.isSymbolicValue())
continue;
if (!v.isKnown())
continue;
if (v.intvalue != 0)
continue;
if (match(v.tokvalue)) {
if (errorPath)
errorPath->insert(errorPath->end(), v.errorPath.begin(), v.errorPath.end());
return true;
}
}
return false;
}
Action analyzeMatch(const Token* tok, Direction d) const { Action analyzeMatch(const Token* tok, Direction d) const {
const Token* parent = tok->astParent(); const Token* parent = tok->astParent();
if (astIsPointer(tok) && (Token::Match(parent, "*|[") || (parent && parent->originalName() == "->")) && getIndirect(tok) <= 0) if (astIsPointer(tok) && (Token::Match(parent, "*|[") || (parent && parent->originalName() == "->")) && getIndirect(tok) <= 0)
@ -2162,6 +2184,8 @@ struct ValueFlowAnalyzer : Analyzer {
return Action::Inconclusive; return Action::Inconclusive;
else else
return a; return a;
} else if (isSameSymbolicValue(ref)) {
return Action::Read | Action::SymbolicMatch;
} }
return Action::None; return Action::None;
} }
@ -2271,6 +2295,13 @@ struct ValueFlowAnalyzer : Analyzer {
ValueFlow::Value* value = getValue(tok); ValueFlow::Value* value = getValue(tok);
if (!value) if (!value)
return; return;
ValueFlow::Value localValue;
if (a.isSymbolicMatch()) {
// Make a copy of the value to modify it
localValue = *value;
value = &localValue;
isSameSymbolicValue(tok, &value->errorPath);
}
// Read first when moving forward // Read first when moving forward
if (d == Direction::Forward && a.isRead()) if (d == Direction::Forward && a.isRead())
setTokenValue(tok, *value, getSettings()); setTokenValue(tok, *value, getSettings());
@ -2319,6 +2350,13 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer {
value.conditional = true; value.conditional = true;
} }
virtual bool useSymbolicValues() const OVERRIDE
{
if (value.isUninitValue())
return false;
return true;
}
virtual void addErrorPath(const Token* tok, const std::string& s) OVERRIDE { virtual void addErrorPath(const Token* tok, const std::string& s) OVERRIDE {
value.errorPath.emplace_back(tok, s); value.errorPath.emplace_back(tok, s);
} }
@ -3002,6 +3040,26 @@ static bool isNotEqual(std::pair<const Token*, const Token*> x, const ValueType*
return isNotEqual(x, y->originalTypeName); return isNotEqual(x, y->originalTypeName);
} }
static bool isDifferentType(const Token* src, const Token* dst)
{
const Type* t = Token::typeOf(src);
const Type* parentT = Token::typeOf(dst);
if (t && parentT) {
if (t->classDef && parentT->classDef && t->classDef != parentT->classDef)
return true;
} else {
std::pair<const Token*, const Token*> decl = Token::typeDecl(src);
std::pair<const Token*, const Token*> parentdecl = Token::typeDecl(dst);
if (isNotEqual(decl, parentdecl))
return true;
if (isNotEqual(decl, dst->valueType()))
return true;
if (isNotEqual(parentdecl, src->valueType()))
return true;
}
return false;
}
bool isLifetimeBorrowed(const Token *tok, const Settings *settings) bool isLifetimeBorrowed(const Token *tok, const Settings *settings)
{ {
if (!tok) if (!tok)
@ -3020,21 +3078,8 @@ bool isLifetimeBorrowed(const Token *tok, const Settings *settings)
return false; return false;
} }
if (Token::Match(tok->astParent(), "return|(|{|%assign%")) { if (Token::Match(tok->astParent(), "return|(|{|%assign%")) {
const Type *t = Token::typeOf(tok); if (isDifferentType(tok, tok->astParent()))
const Type *parentT = Token::typeOf(tok->astParent()); return false;
if (t && parentT) {
if (t->classDef && parentT->classDef && t->classDef != parentT->classDef)
return false;
} else {
std::pair<const Token*, const Token*> decl = Token::typeDecl(tok);
std::pair<const Token*, const Token*> parentdecl = Token::typeDecl(tok->astParent());
if (isNotEqual(decl, parentdecl))
return false;
if (isNotEqual(decl, tok->astParent()->valueType()))
return false;
if (isNotEqual(parentdecl, tok->valueType()))
return false;
}
} }
} else if (Token::Match(tok->astParent()->tokAt(-3), "%var% . push_back|push_front|insert|push (") && } else if (Token::Match(tok->astParent()->tokAt(-3), "%var% . push_back|push_front|insert|push (") &&
astIsContainer(tok->astParent()->tokAt(-3))) { astIsContainer(tok->astParent()->tokAt(-3))) {
@ -4030,6 +4075,28 @@ static void valueFlowConditionExpressions(TokenList *tokenlist, SymbolDatabase*
} }
} }
static bool isTruncated(const ValueType* src, const ValueType* dst, const Settings* settings)
{
if (src->pointer > 0 || dst->pointer > 0)
return src->pointer != dst->pointer;
if (src->smartPointer && dst->smartPointer)
return false;
if ((src->isIntegral() && dst->isIntegral()) || (src->isFloat() && dst->isFloat())) {
size_t srcSize = ValueFlow::getSizeOf(*src, settings);
size_t dstSize = ValueFlow::getSizeOf(*dst, settings);
if (srcSize > dstSize)
return true;
if (srcSize == dstSize && src->sign != dst->sign)
return true;
} else if (src->type == dst->type) {
if (src->type == ValueType::Type::RECORD)
return src->typeScope != dst->typeScope;
} else {
return true;
}
return false;
}
static ValueFlow::Value makeSymbolic(const Token* tok, MathLib::bigint delta = 0) static ValueFlow::Value makeSymbolic(const Token* tok, MathLib::bigint delta = 0)
{ {
ValueFlow::Value value; ValueFlow::Value value;
@ -4060,6 +4127,13 @@ static void valueFlowSymbolic(TokenList* tokenlist, SymbolDatabase* symboldataba
continue; continue;
if (!isConstExpression(tok->astOperand2(), tokenlist->getSettings()->library, true, tokenlist->isCPP())) if (!isConstExpression(tok->astOperand2(), tokenlist->getSettings()->library, true, tokenlist->isCPP()))
continue; continue;
if (tok->astOperand1()->valueType() && tok->astOperand2()->valueType()) {
if (isTruncated(
tok->astOperand2()->valueType(), tok->astOperand1()->valueType(), tokenlist->getSettings()))
continue;
} else if (isDifferentType(tok->astOperand2(), tok->astOperand1())) {
continue;
}
Token* start = nextAfterAstRightmostLeaf(tok); Token* start = nextAfterAstRightmostLeaf(tok);
const Token* end = scope->bodyEnd; const Token* end = scope->bodyEnd;
@ -4345,6 +4419,12 @@ static void valueFlowAfterAssign(TokenList *tokenlist, SymbolDatabase* symboldat
values.remove_if([&](const ValueFlow::Value& value) { values.remove_if([&](const ValueFlow::Value& value) {
return value.valueType == ValueFlow::Value::ValueType::CONTAINER_SIZE; return value.valueType == ValueFlow::Value::ValueType::CONTAINER_SIZE;
}); });
// Remove symbolic values that are the same as the LHS
values.remove_if([&](const ValueFlow::Value& value) {
if (value.isSymbolicValue() && value.tokvalue)
return value.tokvalue->exprId() == tok->astOperand1()->exprId();
return false;
});
// If assignment copy by value, remove Uninit values.. // If assignment copy by value, remove Uninit values..
if ((tok->astOperand1()->valueType() && tok->astOperand1()->valueType()->pointer == 0) || if ((tok->astOperand1()->valueType() && tok->astOperand1()->valueType()->pointer == 0) ||
(tok->astOperand1()->variable() && tok->astOperand1()->variable()->isReference() && tok->astOperand1()->variable()->nameToken() == tok->astOperand1())) (tok->astOperand1()->variable() && tok->astOperand1()->variable()->isReference() && tok->astOperand1()->variable()->nameToken() == tok->astOperand1()))

View File

@ -116,6 +116,7 @@ private:
TEST_CASE(clarifyCondition8); TEST_CASE(clarifyCondition8);
TEST_CASE(alwaysTrue); TEST_CASE(alwaysTrue);
TEST_CASE(alwaysTrueSymbolic);
TEST_CASE(alwaysTrueInfer); TEST_CASE(alwaysTrueInfer);
TEST_CASE(multiConditionAlwaysTrue); TEST_CASE(multiConditionAlwaysTrue);
TEST_CASE(duplicateCondition); TEST_CASE(duplicateCondition);
@ -3757,6 +3758,26 @@ private:
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (style) Condition '!a' is always true\n", errout.str()); ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (style) Condition '!a' is always true\n", errout.str());
} }
void alwaysTrueSymbolic()
{
check("void f(const uint32_t x) {\n"
" uint32_t y[1];\n"
" y[0]=x;\n"
" if(x > 0 || y[0] < 42){}\n"
"}\n");
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:4]: (style) Condition 'y[0]<42' is always true\n", errout.str());
check("struct a {\n"
" a *b() const;\n"
"} c;\n"
"void d() {\n"
" a *e = nullptr;\n"
" e = c.b();\n"
" if (e) {}\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
void alwaysTrueInfer() { void alwaysTrueInfer() {
check("void f(int x) {\n" check("void f(int x) {\n"
" if (x > 5) {\n" " if (x > 5) {\n"

View File

@ -2222,7 +2222,10 @@ private:
" first = first->next();\n" " first = first->next();\n"
" first->str();\n" " first->str();\n"
"}\n"); "}\n");
ASSERT_EQUALS("[test.cpp:8] -> [test.cpp:10]: (warning) Either the condition 'first' is redundant or there is possible null pointer dereference: first.\n", errout.str()); TODO_ASSERT_EQUALS(
"[test.cpp:8] -> [test.cpp:10]: (warning) Either the condition 'first' is redundant or there is possible null pointer dereference: first.\n",
"",
errout.str());
} }
void nullpointer71() { void nullpointer71() {

View File

@ -4489,28 +4489,43 @@ private:
" *c++;\n" " *c++;\n"
" return c;\n" " return c;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n", errout.str()); ASSERT_EQUALS(
"[test.cpp:2]: (warning, inconclusive) Found suspicious operator '*'\n"
"[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n",
errout.str());
check("char* f(char** c) {\n" check("char* f(char** c) {\n"
" *c[5]--;\n" " *c[5]--;\n"
" return *c;\n" " return *c;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n", errout.str()); ASSERT_EQUALS(
"[test.cpp:2]: (warning, inconclusive) Found suspicious operator '*'\n"
"[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n",
errout.str());
check("void f(Foo f) {\n" check("void f(Foo f) {\n"
" *f.a++;\n" " *f.a++;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n", errout.str()); ASSERT_EQUALS(
"[test.cpp:2]: (warning, inconclusive) Found suspicious operator '*'\n"
"[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n",
errout.str());
check("void f(Foo f) {\n" check("void f(Foo f) {\n"
" *f.a[5].v[3]++;\n" " *f.a[5].v[3]++;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n", errout.str()); ASSERT_EQUALS(
"[test.cpp:2]: (warning, inconclusive) Found suspicious operator '*'\n"
"[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n",
errout.str());
check("void f(Foo f) {\n" check("void f(Foo f) {\n"
" *f.a(1, 5).v[x + y]++;\n" " *f.a(1, 5).v[x + y]++;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n", errout.str()); ASSERT_EQUALS(
"[test.cpp:2]: (warning, inconclusive) Found suspicious operator '*'\n"
"[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n",
errout.str());
check("char* f(char* c) {\n" check("char* f(char* c) {\n"
" (*c)++;\n" " (*c)++;\n"
@ -4527,13 +4542,19 @@ private:
" ***c++;\n" " ***c++;\n"
" return c;\n" " return c;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n", errout.str()); ASSERT_EQUALS(
"[test.cpp:2]: (warning, inconclusive) Found suspicious operator '*'\n"
"[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n",
errout.str());
check("char** f(char*** c) {\n" check("char** f(char*** c) {\n"
" **c[5]--;\n" " **c[5]--;\n"
" return **c;\n" " return **c;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n", errout.str()); ASSERT_EQUALS(
"[test.cpp:2]: (warning, inconclusive) Found suspicious operator '*'\n"
"[test.cpp:2]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n",
errout.str());
check("char*** f(char*** c) {\n" check("char*** f(char*** c) {\n"
" (***c)++;\n" " (***c)++;\n"

View File

@ -1187,7 +1187,7 @@ private:
ASSERT_EQUALS("2,x is assigned 'y' here.\n" ASSERT_EQUALS("2,x is assigned 'y' here.\n"
"5,Assuming that condition 'y==32' is not redundant\n" "5,Assuming that condition 'y==32' is not redundant\n"
"4,Compound assignment '+=', assigned value is 20\n" "4,Compound assignment '+=', assigned value is 20\n"
"2,Assignment 'x=y', assigned value is 20\n", "2,x is assigned 'y' here.\n",
getErrorPathForX(code, 3U)); getErrorPathForX(code, 3U));
code = "void f1(int x) {\n" code = "void f1(int x) {\n"