Skip to content

Commit

Permalink
Fix 11579: false negative: knownConditionTrueFalse with non-bool as b…
Browse files Browse the repository at this point in the history
…ool parameter (danmar#5349)

This adds a new checker to check for pointer to bool conversions that
are always known. I removed the previous knownConditionTrueFalse checks
since this was too noisy.
  • Loading branch information
pfultz2 authored Aug 20, 2023
1 parent a5cfa85 commit 03b952d
Show file tree
Hide file tree
Showing 14 changed files with 181 additions and 65 deletions.
19 changes: 16 additions & 3 deletions lib/astutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -696,9 +696,10 @@ std::vector<ValueType> getParentValueTypes(const Token* tok, const Settings* set
return {*tok->astParent()->astOperand1()->valueType()};
return {};
}
const Token* ftok = nullptr;
if (Token::Match(tok->astParent(), "(|{|,")) {
int argn = -1;
const Token* ftok = getTokenArgumentFunction(tok, argn);
ftok = getTokenArgumentFunction(tok, argn);
const Token* typeTok = nullptr;
if (ftok && argn >= 0) {
if (ftok->function()) {
Expand Down Expand Up @@ -741,6 +742,9 @@ std::vector<ValueType> getParentValueTypes(const Token* tok, const Settings* set
ValueType vtParent = ValueType::parseDecl(vtCont->containerTypeToken, *settings);
return {std::move(vtParent)};
}
// The return type of a function is not the parent valuetype
if (Token::simpleMatch(tok->astParent(), "(") && ftok && !tok->isCast() && ftok->tokType() != Token::eType)
return {};
if (Token::Match(tok->astParent(), "return|(|{|%assign%") && parent) {
*parent = tok->astParent();
}
Expand Down Expand Up @@ -1422,7 +1426,7 @@ static bool isForLoopIncrement(const Token* const tok)
parent->astParent()->astParent()->astOperand1()->str() == "for";
}

bool isUsedAsBool(const Token* const tok)
bool isUsedAsBool(const Token* const tok, const Settings* settings)
{
if (!tok)
return false;
Expand All @@ -1435,6 +1439,15 @@ bool isUsedAsBool(const Token* const tok)
const Token* parent = tok->astParent();
if (!parent)
return false;
if (Token::simpleMatch(parent, "["))
return false;
if (parent->isUnaryOp("*"))
return false;
if (Token::simpleMatch(parent, ".")) {
if (astIsRHS(tok))
return isUsedAsBool(parent, settings);
return false;
}
if (Token::Match(parent, "&&|!|%oror%"))
return true;
if (parent->isCast())
Expand All @@ -1451,7 +1464,7 @@ bool isUsedAsBool(const Token* const tok)
if (isForLoopCondition(tok))
return true;
if (!Token::Match(parent, "%cop%")) {
std::vector<ValueType> vtParents = getParentValueTypes(tok);
std::vector<ValueType> vtParents = getParentValueTypes(tok, settings);
return std::any_of(vtParents.cbegin(), vtParents.cend(), [&](const ValueType& vt) {
return vt.pointer == 0 && vt.type == ValueType::BOOL;
});
Expand Down
2 changes: 1 addition & 1 deletion lib/astutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ const Token* isInLoopCondition(const Token* tok);
/**
* Is token used as boolean, that is to say cast to a bool, or used as a condition in a if/while/for
*/
CPPCHECKLIB bool isUsedAsBool(const Token * const tok);
CPPCHECKLIB bool isUsedAsBool(const Token* const tok, const Settings* settings = nullptr);

/**
* Is token passed to a function taking a bool argument
Expand Down
15 changes: 8 additions & 7 deletions lib/checkcondition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ bool CheckCondition::diag(const Token* tok, bool insert)
return false;
const Token* parent = tok->astParent();
bool hasParent = false;
while (Token::Match(parent, "&&|%oror%")) {
while (Token::Match(parent, "!|&&|%oror%")) {
if (mCondDiags.count(parent) != 0) {
hasParent = true;
break;
Expand Down Expand Up @@ -410,6 +410,8 @@ void CheckCondition::comparison()

void CheckCondition::comparisonError(const Token *tok, const std::string &bitop, MathLib::bigint value1, const std::string &op, MathLib::bigint value2, bool result)
{
if (tok && (diag(tok) | diag(tok->astParent())))
return;
std::ostringstream expression;
expression << std::hex << "(X " << bitop << " 0x" << value1 << ") " << op << " 0x" << value2;

Expand Down Expand Up @@ -1370,6 +1372,8 @@ void CheckCondition::checkModuloAlwaysTrueFalse()

void CheckCondition::moduloAlwaysTrueFalseError(const Token* tok, const std::string& maxVal)
{
if (diag(tok))
return;
reportError(tok, Severity::warning, "moduloAlwaysTrueFalse",
"Comparison of modulo result is predetermined, because it is always less than " + maxVal + ".", CWE398, Certainty::normal);
}
Expand Down Expand Up @@ -1466,7 +1470,7 @@ void CheckCondition::alwaysTrueFalse()
for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (Token::simpleMatch(tok, "<") && tok->link()) // don't write false positives when templates are used
continue;
if (!tok->hasKnownBoolValue())
if (!tok->hasKnownIntValue())
continue;
if (Token::Match(tok->previous(), "%name% (") && tok->previous()->function()) {
const Function* f = tok->previous()->function();
Expand All @@ -1490,7 +1494,7 @@ void CheckCondition::alwaysTrueFalse()
else if (parent->str() == ";" && parent->astParent() && parent->astParent()->astParent() &&
Token::simpleMatch(parent->astParent()->astParent()->previous(), "for ("))
condition = parent->astParent()->astParent()->previous();
else if (isBooleanFuncArg(tok))
else if (Token::Match(tok, "%comp%"))
condition = tok;
else
continue;
Expand Down Expand Up @@ -1582,10 +1586,7 @@ void CheckCondition::alwaysTrueFalse()
if (hasSizeof)
continue;

auto it = std::find_if(tok->values().begin(), tok->values().end(), [](const ValueFlow::Value& v) {
return v.isIntValue();
});
alwaysTrueFalseError(tok, condition, &*it);
alwaysTrueFalseError(tok, condition, &tok->values().front());
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/checkcondition.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ class CPPCHECKLIB CheckCondition : public Check {
checkCondition.checkPointerAdditionResultNotNull();
checkCondition.checkDuplicateConditionalAssign();
checkCondition.assignIf();
checkCondition.alwaysTrueFalse();
checkCondition.checkBadBitmaskCheck();
checkCondition.comparison();
checkCondition.checkModuloAlwaysTrueFalse();
checkCondition.checkAssignmentInCondition();
checkCondition.checkCompareValueOutOfTypeRange();
checkCondition.alwaysTrueFalse();
}

/** mismatching assignment / comparison */
Expand Down
100 changes: 69 additions & 31 deletions lib/checkother.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3640,6 +3640,21 @@ static bool isVariableExpression(const Token* tok)
return false;
}

static bool isVariableExprHidden(const Token* tok)
{
if (!tok)
return false;
if (!tok->astParent())
return false;
if (Token::simpleMatch(tok->astParent(), "*") && Token::simpleMatch(tok->astSibling(), "0"))
return true;
if (Token::simpleMatch(tok->astParent(), "&&") && Token::simpleMatch(tok->astSibling(), "false"))
return true;
if (Token::simpleMatch(tok->astParent(), "||") && Token::simpleMatch(tok->astSibling(), "true"))
return true;
return false;
}

void CheckOther::checkKnownArgument()
{
if (!mSettings->severity.isEnabled(Severity::style))
Expand Down Expand Up @@ -3679,44 +3694,25 @@ void CheckOther::checkKnownArgument()
mTokenizer->isCPP(), true, tok->astOperand1(), tok->astOperand2(), mSettings->library, true, true))
continue;
// ensure that there is a integer variable in expression with unknown value
std::string varexpr;
bool isVariableExprHidden = false; // Is variable expression explicitly hidden
auto setVarExpr = [&varexpr, &isVariableExprHidden](const Token *child) {
const Token* vartok = findAstNode(tok, [](const Token* child) {
if (Token::Match(child, "%var%|.|[")) {
if (child->valueType() && child->valueType()->pointer == 0 && child->valueType()->isIntegral() && child->values().empty()) {
varexpr = child->expressionString();
return ChildrenToVisit::done;
}
return ChildrenToVisit::none;
return astIsIntegral(child, false) && !astIsPointer(child) && child->values().empty();
}
if (Token::simpleMatch(child->previous(), "sizeof ("))
return ChildrenToVisit::none;

// hide variable explicitly with 'x * 0' etc
if (!isVariableExprHidden) {
if (Token::simpleMatch(child, "*") && (Token::simpleMatch(child->astOperand1(), "0") || Token::simpleMatch(child->astOperand2(), "0")))
return ChildrenToVisit::none;
if (Token::simpleMatch(child, "&&") && (Token::simpleMatch(child->astOperand1(), "false") || Token::simpleMatch(child->astOperand2(), "false")))
return ChildrenToVisit::none;
if (Token::simpleMatch(child, "||") && (Token::simpleMatch(child->astOperand1(), "true") || Token::simpleMatch(child->astOperand2(), "true")))
return ChildrenToVisit::none;
}

return ChildrenToVisit::op1_and_op2;
};
visitAstNodes(tok, setVarExpr);
if (varexpr.empty()) {
isVariableExprHidden = true;
visitAstNodes(tok, setVarExpr);
}
if (varexpr.empty())
return false;
});
if (!vartok)
continue;
if (vartok->astSibling() &&
findAstNode(vartok->astSibling(), [](const Token* child) {
return Token::simpleMatch(child, "sizeof");
}))
continue;
// ensure that function name does not contain "assert"
std::string funcname = tok->astParent()->previous()->str();
std::string funcname = ftok->str();
strTolower(funcname);
if (funcname.find("assert") != std::string::npos)
continue;
knownArgumentError(tok, ftok, &tok->values().front(), varexpr, isVariableExprHidden);
knownArgumentError(tok, ftok, &tok->values().front(), vartok->expressionString(), isVariableExprHidden(vartok));
}
}
}
Expand Down Expand Up @@ -3747,6 +3743,48 @@ void CheckOther::knownArgumentError(const Token *tok, const Token *ftok, const V
reportError(errorPath, Severity::style, id, errmsg, CWE570, Certainty::normal);
}

void CheckOther::checkKnownPointerToBool()
{
if (!mSettings->severity.isEnabled(Severity::style))
return;
const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope* functionScope : symbolDatabase->functionScopes) {
for (const Token* tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) {
if (!tok->hasKnownIntValue())
continue;
if (!astIsPointer(tok))
continue;
if (Token::Match(tok->astParent(), "?|!|&&|%oror%|%comp%"))
continue;
if (tok->astParent() && Token::Match(tok->astParent()->previous(), "if|while|switch|sizeof ("))
continue;
if (tok->isExpandedMacro())
continue;
if (findParent(tok, [](const Token* parent) {
return parent->isExpandedMacro();
}))
continue;
if (!isUsedAsBool(tok, mSettings))
continue;
const ValueFlow::Value& value = tok->values().front();
knownPointerToBoolError(tok, &value);
}
}
}

void CheckOther::knownPointerToBoolError(const Token* tok, const ValueFlow::Value* value)
{
if (!tok) {
reportError(tok, Severity::style, "knownPointerToBool", "Pointer expression 'p' converted to bool is always true.");
return;
}
std::string cond = value->intvalue ? "true" : "false";
const std::string& expr = tok->expressionString();
std::string errmsg = "Pointer expression '" + expr + "' converted to bool is always " + cond + ".";
const ErrorPath errorPath = getErrorPath(tok, value, errmsg);
reportError(errorPath, Severity::style, "knownPointerToBool", errmsg, CWE570, Certainty::normal);
}

void CheckOther::checkComparePointers()
{
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
Expand Down
5 changes: 5 additions & 0 deletions lib/checkother.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class CPPCHECKLIB CheckOther : public Check {
checkOther.checkFuncArgNamesDifferent();
checkOther.checkShadowVariables();
checkOther.checkKnownArgument();
checkOther.checkKnownPointerToBool();
checkOther.checkComparePointers();
checkOther.checkIncompleteStatement();
checkOther.checkRedundantCopy();
Expand Down Expand Up @@ -218,6 +219,8 @@ class CPPCHECKLIB CheckOther : public Check {

void checkKnownArgument();

void checkKnownPointerToBool();

void checkComparePointers();

void checkModuloOfOne();
Expand Down Expand Up @@ -279,6 +282,7 @@ class CPPCHECKLIB CheckOther : public Check {
void funcArgOrderDifferent(const std::string & functionName, const Token * declaration, const Token * definition, const std::vector<const Token*> & declarations, const std::vector<const Token*> & definitions);
void shadowError(const Token *var, const Token *shadowed, std::string type);
void knownArgumentError(const Token *tok, const Token *ftok, const ValueFlow::Value *value, const std::string &varexpr, bool isVariableExpressionHidden);
void knownPointerToBoolError(const Token* tok, const ValueFlow::Value* value);
void comparePointersError(const Token *tok, const ValueFlow::Value *v1, const ValueFlow::Value *v2);
void checkModuloOfOneError(const Token *tok);

Expand Down Expand Up @@ -348,6 +352,7 @@ class CPPCHECKLIB CheckOther : public Check {
c.shadowError(nullptr, nullptr, "function");
c.shadowError(nullptr, nullptr, "argument");
c.knownArgumentError(nullptr, nullptr, nullptr, "x", false);
c.knownPointerToBoolError(nullptr, nullptr);
c.comparePointersError(nullptr, nullptr, nullptr);
c.redundantAssignmentError(nullptr, nullptr, "var", false);
c.redundantInitializationError(nullptr, nullptr, "var", false);
Expand Down
2 changes: 1 addition & 1 deletion lib/forwardanalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ struct ForwardTraversal {
if (allAnalysis.isIncremental())
return Break(Analyzer::Terminate::Bail);
} else if (allAnalysis.isModified()) {
std::vector<ForwardTraversal> ftv = tryForkScope(endBlock, /*isModified*/ true);
std::vector<ForwardTraversal> ftv = tryForkScope(endBlock, allAnalysis.isModified());
bool forkContinue = true;
for (ForwardTraversal& ft : ftv) {
if (condTok)
Expand Down
9 changes: 0 additions & 9 deletions lib/token.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2388,15 +2388,6 @@ bool Token::hasKnownIntValue() const
});
}

bool Token::hasKnownBoolValue() const
{
if (!mImpl->mValues)
return false;
return std::any_of(mImpl->mValues->begin(), mImpl->mValues->end(), [](const ValueFlow::Value& value) {
return value.isIntValue() && (value.isKnown() || (value.intvalue == 0 && value.isImpossible()));
});
}

bool Token::hasKnownValue() const
{
return mImpl->mValues && std::any_of(mImpl->mValues->begin(), mImpl->mValues->end(), std::mem_fn(&ValueFlow::Value::isKnown));
Expand Down
1 change: 0 additions & 1 deletion lib/token.h
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,6 @@ class CPPCHECKLIB Token {
}

bool hasKnownIntValue() const;
bool hasKnownBoolValue() const;
bool hasKnownValue() const;
bool hasKnownValue(ValueFlow::Value::ValueType t) const;
bool hasKnownSymbolicValue(const Token* tok) const;
Expand Down
3 changes: 2 additions & 1 deletion lib/valueflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6843,7 +6843,8 @@ static void valueFlowInferCondition(TokenList& tokenlist,
}
}
} else if (Token::Match(tok->astParent(), "?|&&|!|%oror%") ||
Token::Match(tok->astParent()->previous(), "if|while (")) {
Token::Match(tok->astParent()->previous(), "if|while (") ||
(astIsPointer(tok) && isUsedAsBool(tok, settings))) {
std::vector<ValueFlow::Value> result = infer(IntegralInferModel{}, "!=", tok->values(), 0);
if (result.size() != 1)
continue;
Expand Down
1 change: 1 addition & 0 deletions releasenotes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Release Notes for Cppcheck 2.12

New checks:
- uselessOverride finds overriding functions that either duplicate code from or delegate back to the base class implementation
- knownPointerToBool finds pointer to bool conversions that are always true or false

Improved checking:
- truncLongCastAssignment and truncLongCastReturn check additional types, including float/double/long double
Expand Down
2 changes: 1 addition & 1 deletion test/testastutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ class TestAstUtils : public TestFixture {
ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i + 2) {} }", "i +"));
ASSERT(Result::True == isUsedAsBool("void f() { int i; bool b = i; }", "i ; }"));
ASSERT(Result::True == isUsedAsBool("void f(bool b); void f() { int i; f(i); }","i )"));
ASSERT(Result::True == isUsedAsBool("void f() { int *i; if (*i) {} }", "i )"));
ASSERT(Result::False == isUsedAsBool("void f() { int *i; if (*i) {} }", "i )"));
ASSERT(Result::True == isUsedAsBool("void f() { int *i; if (*i) {} }", "* i )"));
ASSERT(Result::True == isUsedAsBool("int g(); void h(bool); void f() { h(g()); }", "( ) )"));
ASSERT(Result::True == isUsedAsBool("int g(int); void h(bool); void f() { h(g(0)); }", "( 0 ) )"));
Expand Down
Loading

0 comments on commit 03b952d

Please sign in to comment.