/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2026 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
//---------------------------------------------------------------------------
// Check for condition mismatches
//---------------------------------------------------------------------------
#include "checkcondition.h"
#include "astutils.h"
#include "library.h"
#include "platform.h"
#include "settings.h"
#include "symboldatabase.h"
#include "token.h"
#include "tokenize.h"
#include "utils.h"
#include "vfvalue.h"
#include "checkother.h" // comparisonNonZeroExpressionLessThanZero and testIfNonZeroExpressionIsPositive
#include
#include
#include
#include
#include
#include
#include
#include
#include
// CWE ids used
static const CWE uncheckedErrorConditionCWE(391U);
static const CWE CWE398(398U); // Indicator of Poor Code Quality
static const CWE CWE570(570U); // Expression is Always False
static const CWE CWE571(571U); // Expression is Always True
//---------------------------------------------------------------------------
bool CheckConditionImpl::diag(const Token* tok, bool insert)
{
if (!tok)
return false;
const Token* parent = tok->astParent();
bool hasParent = false;
while (Token::Match(parent, "!|&&|%oror%")) {
if (mCondDiags.count(parent) != 0) {
hasParent = true;
break;
}
parent = parent->astParent();
}
if (mCondDiags.count(tok) == 0 && !hasParent) {
if (insert)
mCondDiags.insert(tok);
return false;
}
return true;
}
bool CheckConditionImpl::diag(const Token* tok1, const Token* tok2)
{
const bool b1 = diag(tok1);
const bool b2 = diag(tok2);
return b1 && b2;
}
bool CheckConditionImpl::isAliased(const std::set &vars) const
{
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (Token::Match(tok, "= & %var% ;") && vars.find(tok->tokAt(2)->varId()) != vars.end())
return true;
}
return false;
}
void CheckConditionImpl::assignIf()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("assignIfError"))
return;
logChecker("CheckCondition::assignIf"); // style
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (tok->str() != "=")
continue;
if (Token::Match(tok->tokAt(-2), "[;{}] %var% =")) {
const Variable *var = tok->previous()->variable();
if (var == nullptr)
continue;
char bitop = '\0';
MathLib::bigint num = 0;
if (Token::Match(tok->next(), "%num% [&|]")) {
bitop = tok->strAt(2).at(0);
num = MathLib::toBigNumber(tok->tokAt(1));
} else {
const Token *endToken = Token::findsimplematch(tok, ";");
// Casting address
if (endToken && Token::Match(endToken->tokAt(-4), "* ) & %any% ;"))
endToken = nullptr;
if (endToken && Token::Match(endToken->tokAt(-2), "[&|] %num% ;")) {
bitop = endToken->strAt(-2).at(0);
num = MathLib::toBigNumber(endToken->tokAt(-1));
}
}
if (bitop == '\0')
continue;
if (num < 0 && bitop == '|')
continue;
assignIfParseScope(tok, tok->tokAt(4), var->declarationId(), var->isLocal(), bitop, num);
}
}
}
static bool isParameterChanged(const Token *partok)
{
bool addressOf = Token::Match(partok, "[(,] &");
int argumentNumber = 0;
const Token *ftok;
for (ftok = partok; ftok && ftok->str() != "("; ftok = ftok->previous()) {
if (ftok->str() == ")")
ftok = ftok->link();
else if (argumentNumber == 0U && ftok->str() == "&")
addressOf = true;
else if (ftok->str() == ",")
argumentNumber++;
}
ftok = ftok ? ftok->previous() : nullptr;
if (!(ftok && ftok->function()))
return true;
const Variable *par = ftok->function()->getArgumentVar(argumentNumber);
if (!par)
return true;
if (par->isConst())
return false;
if (addressOf || par->isReference() || par->isPointer())
return true;
return false;
}
/** parse scopes recursively */
bool CheckConditionImpl::assignIfParseScope(const Token * const assignTok,
const Token * const startTok,
const nonneg int varid,
const bool islocal,
const char bitop,
const MathLib::bigint num)
{
bool ret = false;
for (const Token *tok2 = startTok; tok2; tok2 = tok2->next()) {
if ((bitop == '&') && Token::Match(tok2->tokAt(2), "%varid% %cop% %num% ;", varid) && tok2->strAt(3) == std::string(1U, bitop)) {
const MathLib::bigint num2 = MathLib::toBigNumber(tok2->tokAt(4));
if (0 == (num & num2))
mismatchingBitAndError(assignTok, num, tok2, num2);
}
if (Token::Match(tok2, "%varid% =", varid)) {
return true;
}
if (bitop == '&' && Token::Match(tok2, "%varid% &= %num% ;", varid)) {
const MathLib::bigint num2 = MathLib::toBigNumber(tok2->tokAt(2));
if (0 == (num & num2))
mismatchingBitAndError(assignTok, num, tok2, num2);
}
if (Token::Match(tok2, "++|-- %varid%", varid) || Token::Match(tok2, "%varid% ++|--", varid))
return true;
if (Token::Match(tok2, "[(,] &| %varid% [,)]", varid) && isParameterChanged(tok2))
return true;
if (tok2->str() == "}")
return false;
if (Token::Match(tok2, "break|continue|return"))
ret = true;
if (ret && tok2->str() == ";")
return false;
if (!islocal && Token::Match(tok2, "%name% (") && !Token::simpleMatch(tok2->linkAt(1), ") {"))
return true;
if (Token::Match(tok2, "if|while (")) {
if (!islocal && tok2->str() == "while")
continue;
if (tok2->str() == "while") {
// is variable changed in loop?
const Token *bodyStart = tok2->linkAt(1)->next();
const Token *bodyEnd = bodyStart ? bodyStart->link() : nullptr;
if (!bodyEnd || bodyEnd->str() != "}" || isVariableChanged(bodyStart, bodyEnd, varid, !islocal, mSettings))
continue;
}
// parse condition
const Token * const end = tok2->linkAt(1);
for (; tok2 != end; tok2 = tok2->next()) {
if (Token::Match(tok2, "[(,] &| %varid% [,)]", varid)) {
return true;
}
if (Token::Match(tok2,"&&|%oror%|( %varid% ==|!= %num% &&|%oror%|)", varid)) {
const Token *vartok = tok2->next();
const MathLib::bigint num2 = MathLib::toBigNumber(vartok->tokAt(2));
if ((num & num2) != ((bitop=='&') ? num2 : num)) {
const std::string& op(vartok->strAt(1));
const bool alwaysTrue = op == "!=";
const std::string condition(vartok->str() + op + vartok->strAt(2));
assignIfError(assignTok, tok2, condition, alwaysTrue);
}
}
if (Token::Match(tok2, "%varid% %op%", varid) && tok2->next()->isAssignmentOp()) {
return true;
}
}
const bool ret1 = assignIfParseScope(assignTok, end->tokAt(2), varid, islocal, bitop, num);
bool ret2 = false;
if (Token::simpleMatch(end->linkAt(1), "} else {"))
ret2 = assignIfParseScope(assignTok, end->linkAt(1)->tokAt(3), varid, islocal, bitop, num);
if (ret1 || ret2)
return true;
}
}
return false;
}
void CheckConditionImpl::assignIfError(const Token *tok1, const Token *tok2, const std::string &condition, bool result)
{
if (tok2 && diag(tok2->tokAt(2)))
return;
std::list locations = { tok1, tok2 };
reportError(locations,
Severity::style,
"assignIfError",
"Mismatching assignment and comparison, comparison '" + condition + "' is always " + std::string(bool_to_string(result)) + ".", CWE398, Certainty::normal);
}
void CheckConditionImpl::mismatchingBitAndError(const Token *tok1, const MathLib::bigint num1, const Token *tok2, const MathLib::bigint num2)
{
std::list locations = { tok1, tok2 };
std::ostringstream msg;
msg astOperand1()->str())
getnumchildren(tok->astOperand1(), numchildren);
if (tok->astOperand2() && tok->astOperand2()->isNumber())
numchildren.push_back(MathLib::toBigNumber(tok->astOperand2()));
else if (tok->astOperand2() && tok->str() == tok->astOperand2()->str())
getnumchildren(tok->astOperand2(), numchildren);
}
/* Return whether tok is in the body for a function returning a boolean. */
static bool inBooleanFunction(const Token *tok)
{
const Scope *scope = tok ? tok->scope() : nullptr;
while (scope && scope->isLocal())
scope = scope->nestedIn;
if (scope && scope->type == ScopeType::eFunction) {
const Function *func = scope->function;
if (func) {
const Token *ret = func->retDef;
while (Token::Match(ret, "static|const"))
ret = ret->next();
return Token::Match(ret, "bool|_Bool");
}
}
return false;
}
static bool isOperandExpanded(const Token *tok)
{
if (tok->isExpandedMacro() || tok->isEnumerator())
return true;
if (tok->astOperand1() && isOperandExpanded(tok->astOperand1()))
return true;
if (tok->astOperand2() && isOperandExpanded(tok->astOperand2()))
return true;
return false;
}
void CheckConditionImpl::checkBadBitmaskCheck()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("badBitmaskCheck"))
return;
logChecker("CheckCondition::checkBadBitmaskCheck"); // style
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (tok->str() == "|" && tok->astOperand1() && tok->astOperand2() && tok->astParent()) {
const Token* parent = tok->astParent();
const bool isBoolean = Token::Match(parent, "&&|%oror%") ||
(parent->str() == "?" && parent->astOperand1() == tok) ||
(parent->str() == "=" && parent->astOperand2() == tok && parent->astOperand1() && parent->astOperand1()->variable() && Token::Match(parent->astOperand1()->variable()->typeStartToken(), "bool|_Bool")) ||
(parent->str() == "(" && Token::Match(parent->astOperand1(), "if|while")) ||
(parent->str() == "return" && parent->astOperand1() == tok && inBooleanFunction(tok));
const bool isTrue = (tok->astOperand1()->hasKnownIntValue() && tok->astOperand1()->getKnownIntValue() != 0) ||
(tok->astOperand2()->hasKnownIntValue() && tok->astOperand2()->getKnownIntValue() != 0);
if (isBoolean && isTrue)
badBitmaskCheckError(tok);
// If there are #ifdef in the expression don't warn about redundant | to avoid FP
const auto& startStop = tok->findExpressionStartEndTokens();
if (mTokenizer->hasIfdef(startStop.first, startStop.second))
continue;
const bool isZero1 = (tok->astOperand1()->hasKnownIntValue() && tok->astOperand1()->getKnownIntValue() == 0);
const bool isZero2 = (tok->astOperand2()->hasKnownIntValue() && tok->astOperand2()->getKnownIntValue() == 0);
if (!isZero1 && !isZero2)
continue;
if (!tok->isExpandedMacro() &&
!(isZero1 && isOperandExpanded(tok->astOperand1())) &&
!(isZero2 && isOperandExpanded(tok->astOperand2())))
badBitmaskCheckError(tok, /*isNoOp*/ true);
}
}
}
void CheckConditionImpl::badBitmaskCheckError(const Token *tok, bool isNoOp)
{
if (isNoOp)
reportError(tok, Severity::style, "badBitmaskCheck", "Operator '|' with one operand equal to zero is redundant.", CWE571, Certainty::normal);
else
reportError(tok, Severity::warning, "badBitmaskCheck", "Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'?", CWE571, Certainty::normal);
}
void CheckConditionImpl::comparison()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("comparisonError"))
return;
logChecker("CheckCondition::comparison"); // style
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (!tok->isComparisonOp())
continue;
const Token *expr1 = tok->astOperand1();
const Token *expr2 = tok->astOperand2();
if (!expr1 || !expr2)
continue;
if (expr1->hasKnownIntValue())
std::swap(expr1,expr2);
if (!expr2->hasKnownIntValue())
continue;
if (!compareTokenFlags(expr1, expr2, /*macro*/ true))
continue;
const MathLib::bigint num2 = expr2->getKnownIntValue();
if (num2 < 0)
continue;
if (!Token::Match(expr1,"[&|]"))
continue;
std::list numbers;
getnumchildren(expr1, numbers);
for (const MathLib::bigint num1 : numbers) {
if (num1 < 0)
continue;
if (Token::Match(tok, "==|!=")) {
if ((expr1->str() == "&" && (num1 & num2) != num2) ||
(expr1->str() == "|" && (num1 | num2) != num2)) {
const std::string& op(tok->str());
comparisonError(expr1, expr1->str(), num1, op, num2, op != "==");
}
} else if (expr1->str() == "&") {
const bool or_equal = Token::Match(tok, ">=|=|=|=|= 7U" is always true for unsigned a
//"(a | 0x07) < 7U" is always false for unsigned a
comparisonError(expr1, expr1->str(), num1, op, num2, or_equal);
} else if ((Token::Match(tok, "")) && (num1 > num2)) {
//"(a | 0x08) 6U" is always true for unsigned a
comparisonError(expr1, expr1->str(), num1, op, num2, !or_equal);
}
}
}
}
}
}
void CheckConditionImpl::comparisonError(const Token *tok, const std::string &bitop, MathLib::bigint value1, const std::string &op, MathLib::bigint value2, bool result)
{
std::ostringstream expression;
expression astOperand1())
return false;
const Token *expr2 = cond2->astOperand1();
const Token *num2 = cond2->astOperand2();
if (!num2->isNumber())
std::swap(expr2,num2);
if (!num2->isNumber() || MathLib::isNegative(num2->str()))
return false;
if (!isSameExpression(true, expr1, expr2, mSettings, pure, false))
return false;
const MathLib::bigint value1 = MathLib::toBigNumber(num1);
const MathLib::bigint value2 = MathLib::toBigNumber(num2);
if (cond2->str() == "&")
return ((value1 & value2) == value2);
return ((value1 & value2) > 0);
}
return false;
}
void CheckConditionImpl::duplicateCondition()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("duplicateCondition"))
return;
logChecker("CheckCondition::duplicateCondition"); // style
const SymbolDatabase *const symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope &scope : symbolDatabase->scopeList) {
if (scope.type != ScopeType::eIf)
continue;
const Token* tok2 = scope.classDef->next();
if (!tok2)
continue;
const Token* cond1 = tok2->astOperand2();
if (!cond1)
continue;
if (cond1->hasKnownIntValue())
continue;
tok2 = tok2->link();
if (!Token::simpleMatch(tok2, ") {"))
continue;
tok2 = tok2->linkAt(1);
if (!Token::simpleMatch(tok2, "} if ("))
continue;
const Token *cond2 = tok2->tokAt(2)->astOperand2();
if (!cond2)
continue;
ErrorPath errorPath;
if (!findExpressionChanged(cond1, scope.classDef->next(), cond2, mSettings) &&
isSameExpression(true, cond1, cond2, mSettings, true, true, &errorPath))
duplicateConditionError(cond1, cond2, std::move(errorPath));
}
}
void CheckConditionImpl::duplicateConditionError(const Token *tok1, const Token *tok2, ErrorPath errorPath)
{
if (diag(tok1, tok2))
return;
errorPath.emplace_back(tok1, "First condition");
errorPath.emplace_back(tok2, "Second condition");
std::string msg = "The if condition is the same as the previous if condition";
reportError(std::move(errorPath), Severity::style, "duplicateCondition", msg, CWE398, Certainty::normal);
}
void CheckConditionImpl::multiCondition()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("multiCondition"))
return;
logChecker("CheckCondition::multiCondition"); // style
const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope &scope : symbolDatabase->scopeList) {
if (scope.type != ScopeType::eIf)
continue;
const Token * const cond1 = scope.classDef->next()->astOperand2();
if (!cond1)
continue;
const Token * tok2 = scope.classDef->next();
// Check each 'else if'
for (;;) {
tok2 = tok2->link();
if (!Token::simpleMatch(tok2, ") {"))
break;
tok2 = tok2->linkAt(1);
if (!Token::simpleMatch(tok2, "} else { if ("))
break;
tok2 = tok2->tokAt(4);
if (tok2->astOperand2()) {
ErrorPath errorPath;
if (isOverlappingCond(cond1, tok2->astOperand2(), true) &&
!findExpressionChanged(cond1, cond1, tok2->astOperand2(), mSettings))
overlappingElseIfConditionError(tok2->astOperand2(), cond1->linenr());
else if (isOppositeCond(
true, cond1, tok2->astOperand2(), mSettings, true, true, &errorPath) &&
!findExpressionChanged(cond1, cond1, tok2->astOperand2(), mSettings))
oppositeElseIfConditionError(cond1, tok2->astOperand2(), std::move(errorPath));
}
}
}
}
void CheckConditionImpl::overlappingElseIfConditionError(const Token *tok, nonneg int line1)
{
if (diag(tok))
return;
std::ostringstream errmsg;
errmsg astOperand1();
while (obj && obj->str() == ".")
obj = obj->astOperand1();
if (!obj)
return true;
if (obj->variable() && obj->variable()->isConst())
return false;
if (ftok->function() && ftok->function()->isConst())
return false;
if (ftok->isControlFlowKeyword())
return false;
return true;
}
static bool isNestedInLambda(const Scope* inner, const Scope* outer)
{
while (inner && inner != outer) {
if (inner->type == ScopeType::eLambda)
return true;
inner = inner->nestedIn;
}
return false;
}
void CheckConditionImpl::multiCondition2()
{
if (!mSettings.severity.isEnabled(Severity::warning) &&
!mSettings.isPremiumEnabled("identicalConditionAfterEarlyExit") &&
!mSettings.isPremiumEnabled("identicalInnerCondition"))
return;
logChecker("CheckCondition::multiCondition2"); // warning
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope &scope : symbolDatabase->scopeList) {
const Token *condTok = nullptr;
if (scope.type == ScopeType::eIf || scope.type == ScopeType::eWhile)
condTok = scope.classDef->next()->astOperand2();
else if (scope.type == ScopeType::eFor) {
condTok = scope.classDef->next()->astOperand2();
if (!condTok || condTok->str() != ";")
continue;
condTok = condTok->astOperand2();
if (!condTok || condTok->str() != ";")
continue;
condTok = condTok->astOperand1();
}
if (!condTok)
continue;
const Token * const cond1 = condTok;
if (!Token::simpleMatch(scope.classDef->linkAt(1), ") {"))
continue;
bool functionCall = false;
bool nonConstFunctionCall = false;
bool nonlocal = false; // nonlocal variable used in condition
std::set vars; // variables used in condition
visitAstNodes(condTok,
[&](const Token *cond) {
if (Token::Match(cond, "%name% (")) {
functionCall = true;
nonConstFunctionCall = isNonConstFunctionCall(cond, mSettings.library);
if (nonConstFunctionCall)
return ChildrenToVisit::done;
}
if (cond->varId()) {
vars.insert(cond->varId());
const Variable *var = cond->variable();
if (!nonlocal && var) {
if (!(var->isLocal() || var->isArgument()))
nonlocal = true;
else if ((var->isPointer() || var->isReference() || var->isArray()) && !Token::Match(cond->astParent(), "%oror%|&&|!"))
// TODO: if var is pointer check what it points at
nonlocal = true;
}
} else if (!nonlocal && cond->isName()) {
// varid is 0. this is possibly a nonlocal variable..
nonlocal = Token::Match(cond->astParent(), "%cop%|(|[") || Token::Match(cond, "%name% .") || (cond->isCpp() && cond->str() == "this");
} else {
return ChildrenToVisit::op1_and_op2;
}
return ChildrenToVisit::none;
});
if (nonConstFunctionCall)
continue;
std::vector varsInCond;
visitAstNodes(condTok,
[&varsInCond](const Token *cond) {
if (cond->variable()) {
const Variable *var = cond->variable();
if (std::find(varsInCond.cbegin(), varsInCond.cend(), var) == varsInCond.cend())
varsInCond.push_back(var);
}
return ChildrenToVisit::op1_and_op2;
});
// parse until second condition is reached..
enum MULTICONDITIONTYPE : std::uint8_t { INNER, AFTER };
const Token *tok;
// Parse inner condition first and then early return condition
std::vector types = {MULTICONDITIONTYPE::INNER};
if (Token::Match(scope.bodyStart, "{ return|throw|continue|break"))
types.push_back(MULTICONDITIONTYPE::AFTER);
for (const MULTICONDITIONTYPE type:types) {
if (type == MULTICONDITIONTYPE::AFTER) {
tok = scope.bodyEnd->next();
} else {
tok = scope.bodyStart;
}
const Token * const endToken = tok->scope()->bodyEnd;
for (; tok && tok != endToken; tok = tok->next()) {
if (isNestedInLambda(tok->scope(), cond1->scope()))
continue;
if (isExpressionChangedAt(cond1, tok, 0, false, mSettings))
break;
if (Token::Match(tok, "if|return")) {
const Token * condStartToken = tok->str() == "if" ? tok->next() : tok;
const Token * condEndToken = tok->str() == "if" ? condStartToken->link() : Token::findsimplematch(condStartToken, ";");
// Does condition modify tracked variables?
if (findExpressionChanged(cond1, condStartToken, condEndToken, mSettings))
break;
// Condition..
const Token *cond2 = tok->str() == "if" ? condStartToken->astOperand2() : condStartToken->astOperand1();
const bool isReturnVar = (tok->str() == "return" && (!Token::Match(cond2, "%cop%") || cond2->isUnaryOp("!") || cond2->isUnaryOp("*")));
ErrorPath errorPath;
if (type == MULTICONDITIONTYPE::INNER) {
visitAstNodes(cond1, [&](const Token* firstCondition) {
if (!firstCondition)
return ChildrenToVisit::none;
if (firstCondition->str() == "&&") {
if (!isOppositeCond(false, firstCondition, cond2, mSettings, true, true))
return ChildrenToVisit::op1_and_op2;
}
if (!firstCondition->hasKnownIntValue()) {
if (!isReturnVar && isOppositeCond(false, firstCondition, cond2, mSettings, true, true, &errorPath)) {
if (!isAliased(vars))
oppositeInnerConditionError(firstCondition, cond2, errorPath);
} else if (!isReturnVar && isSameExpression(true, firstCondition, cond2, mSettings, true, true, &errorPath)) {
identicalInnerConditionError(firstCondition, cond2, errorPath);
} else if (!isReturnVar && isOverlappingCond(cond2, firstCondition, true)) {
overlappingInnerConditionError(firstCondition, cond2, errorPath);
}
}
return ChildrenToVisit::none;
});
} else {
visitAstNodes(cond2, [&](const Token *secondCondition) {
if (secondCondition->str() == "||" || secondCondition->str() == "&&")
return ChildrenToVisit::op1_and_op2;
if ((!cond1->hasKnownIntValue() || !secondCondition->hasKnownIntValue()) &&
isSameExpression(true, cond1, secondCondition, mSettings, true, true, &errorPath)) {
if (!isAliased(vars) && !mTokenizer->hasIfdef(cond1, secondCondition)) {
identicalConditionAfterEarlyExitError(cond1, secondCondition, errorPath);
return ChildrenToVisit::done;
}
}
return ChildrenToVisit::none;
});
}
}
if (Token::Match(tok, "%name% (") &&
isVariablesChanged(tok, tok->linkAt(1), 0, varsInCond, mSettings)) {
break;
}
if (Token::Match(tok, "%type% (") && nonlocal && isNonConstFunctionCall(tok, mSettings.library)) // non const function call -> bailout if there are nonlocal variables
break;
if (Token::Match(tok, "case|break|continue|return|throw") && tok->scope() == endToken->scope())
break;
if (Token::Match(tok, "[;{}] %name% :"))
break;
// bailout if loop is seen.
// TODO: handle loops better.
if (Token::Match(tok, "for|while|do")) {
const Token *tok1 = tok->next();
const Token *tok2;
if (Token::simpleMatch(tok, "do {")) {
if (!Token::simpleMatch(tok->linkAt(1), "} while ("))
break;
tok2 = tok->linkAt(1)->linkAt(2);
} else if (Token::Match(tok, "if|while (")) {
tok2 = tok->linkAt(1);
if (Token::simpleMatch(tok2, ") {"))
tok2 = tok2->linkAt(1);
if (!tok2)
break;
} else {
// Incomplete code
break;
}
const bool changed = std::any_of(vars.cbegin(), vars.cend(), [&](int varid) {
return isVariableChanged(tok1, tok2, varid, nonlocal, mSettings);
});
if (changed)
break;
}
if ((tok->varId() && vars.find(tok->varId()) != vars.end()) ||
(!tok->varId() && nonlocal) ||
(functionCall && tok->variable() && !tok->variable()->isLocal())) {
if (Token::Match(tok, "%name% %assign%|++|--"))
break;
if (Token::Match(tok->astParent(), "*|.|[")) {
const Token *parent = tok;
while (Token::Match(parent->astParent(), ".|[") || (parent->astParent() && parent->astParent()->isUnaryOp("*")))
parent = parent->astParent();
if (Token::Match(parent->astParent(), "%assign%|++|--"))
break;
}
if (tok->isCpp() && Token::Match(tok, "%name% ')
s[0] = 'str() == "&&";
for (int test = 1; test testvalue is less than both value1 and value2
// 2 => testvalue is value1
// 3 => testvalue is between value1 and value2
// 4 => testvalue value2
// 5 => testvalue is larger than both value1 and value2
bool result1, result2;
if (isfloat) {
const auto testvalue = getvalue(test, d1, d2);
result1 = checkFloatRelation(op1, testvalue, d1);
result2 = checkFloatRelation(op2, testvalue, d2);
} else if (useUnsignedInt) {
const auto testvalue = getvalue(test, u1, u2);
result1 = checkIntRelation(op1, testvalue, u1);
result2 = checkIntRelation(op2, testvalue, u2);
} else {
const auto testvalue = getvalue(test, i1, i2);
result1 = checkIntRelation(op1, testvalue, i1);
result2 = checkIntRelation(op2, testvalue, i2);
}
if (not1)
result1 = !result1;
if (not2)
result2 = !result2;
if (isAnd) {
alwaysTrue &= (result1 && result2);
alwaysFalse &= !(result1 && result2);
} else {
alwaysTrue &= (result1 || result2);
alwaysFalse &= !(result1 || result2);
}
firstTrue &= !(!result1 && result2);
secondTrue &= !(result1 && !result2);
}
const std::string cond1str = conditionString(not1, expr1, op1, value1);
const std::string cond2str = conditionString(not2, expr2, op2, value2);
if (printWarning && (alwaysTrue || alwaysFalse)) {
const std::string text = cond1str + " " + tok->str() + " " + cond2str;
incorrectLogicOperatorError(tok, text, alwaysTrue, inconclusive, std::move(errorPath));
} else if (printStyle && (firstTrue || secondTrue)) {
const int which = isfloat ? sufficientCondition(std::move(op1), not1, d1, std::move(op2), not2, d2, isAnd) : sufficientCondition(std::move(op1), not1, i1, std::move(op2), not2, i2, isAnd);
std::string text;
if (which != 0) {
text = "The condition '" + (which == 1 ? cond2str : cond1str) + "' is redundant since '" + (which == 1 ? cond1str : cond2str) + "' is sufficient.";
} else
text = "If '" + (secondTrue ? cond1str : cond2str) + "', the comparison '" + (secondTrue ? cond2str : cond1str) + "' is always true.";
redundantConditionError(tok, text, inconclusive);
}
}
}
}
void CheckConditionImpl::incorrectLogicOperatorError(const Token *tok, const std::string &condition, bool always, bool inconclusive, ErrorPath errors)
{
if (diag(tok))
return;
errors.emplace_back(tok, "");
if (always)
reportError(std::move(errors), Severity::warning, "incorrectLogicOperator",
"Logical disjunction always evaluates to true: " + condition + ".\n"
"Logical disjunction always evaluates to true: " + condition + ". "
"Are these conditions necessary? Did you intend to use && instead? Are the numbers correct? Are you comparing the correct variables?", CWE571, inconclusive ? Certainty::inconclusive : Certainty::normal);
else
reportError(std::move(errors), Severity::warning, "incorrectLogicOperator",
"Logical conjunction always evaluates to false: " + condition + ".\n"
"Logical conjunction always evaluates to false: " + condition + ". "
"Are these conditions necessary? Did you intend to use || instead? Are the numbers correct? Are you comparing the correct variables?", CWE570, inconclusive ? Certainty::inconclusive : Certainty::normal);
}
void CheckConditionImpl::redundantConditionError(const Token *tok, const std::string &text, bool inconclusive)
{
if (diag(tok))
return;
reportError(tok, Severity::style, "redundantCondition", "Redundant condition: " + text, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal);
}
//-----------------------------------------------------------------------------
// Detect "(var % val1) > val2" where val2 is >= val1.
//-----------------------------------------------------------------------------
void CheckConditionImpl::checkModuloAlwaysTrueFalse()
{
if (!mSettings.severity.isEnabled(Severity::warning))
return;
logChecker("CheckCondition::checkModuloAlwaysTrueFalse"); // warning
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (!tok->isComparisonOp())
continue;
const Token *num, *modulo;
if (Token::simpleMatch(tok->astOperand1(), "%") && Token::Match(tok->astOperand2(), "%num%")) {
modulo = tok->astOperand1();
num = tok->astOperand2();
} else if (Token::Match(tok->astOperand1(), "%num%") && Token::simpleMatch(tok->astOperand2(), "%")) {
num = tok->astOperand1();
modulo = tok->astOperand2();
} else {
continue;
}
if (Token::Match(modulo->astOperand2(), "%num%") &&
MathLib::isLessEqual(modulo->astOperand2()->str(), num->str()))
moduloAlwaysTrueFalseError(tok, modulo->astOperand2()->str());
}
}
}
void CheckConditionImpl::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);
}
static int countPar(const Token *tok1, const Token *tok2)
{
int par = 0;
for (const Token *tok = tok1; tok && tok != tok2; tok = tok->next()) {
if (tok->str() == "(")
++par;
else if (tok->str() == ")")
--par;
else if (tok->str() == ";")
return -1;
}
return par;
}
//---------------------------------------------------------------------------
// Clarify condition '(x = a < 0)' into '((x = a) < 0)' or '(x = (a < 0))'
// Clarify condition '(a & b == c)' into '((a & b) == c)' or '(a & (b == c))'
//---------------------------------------------------------------------------
void CheckConditionImpl::clarifyCondition()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("clarifyCondition"))
return;
logChecker("CheckCondition::clarifyCondition"); // style
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (Token::Match(tok, "( %name% [=&|^]")) {
for (const Token *tok2 = tok->tokAt(3); tok2; tok2 = tok2->next()) {
if (tok2->str() == "(" || tok2->str() == "[")
tok2 = tok2->link();
else if (tok2->isComparisonOp()) {
// This might be a template
if (!tok2->isC() && tok2->link())
break;
if (Token::simpleMatch(tok2->astParent(), "?"))
break;
clarifyConditionError(tok, tok->strAt(2) == "=", false);
break;
} else if (!tok2->isName() && !tok2->isNumber() && tok2->str() != ".")
break;
}
} else if (tok->tokType() == Token::eBitOp && !tok->isUnaryOp("&")) {
if (tok->astOperand2() && tok->astOperand2()->variable() && tok->astOperand2()->variable()->nameToken() == tok->astOperand2())
continue;
// using boolean result in bitwise operation ! x [&|^]
const ValueType* vt1 = tok->astOperand1() ? tok->astOperand1()->valueType() : nullptr;
const ValueType* vt2 = tok->astOperand2() ? tok->astOperand2()->valueType() : nullptr;
if (vt1 && vt1->type == ValueType::BOOL && !Token::Match(tok->astOperand1(), "%name%|(|[|::|.") && countPar(tok->astOperand1(), tok) == 0)
clarifyConditionError(tok, false, true);
else if (vt2 && vt2->type == ValueType::BOOL && !Token::Match(tok->astOperand2(), "%name%|(|[|::|.") && countPar(tok, tok->astOperand2()) == 0)
clarifyConditionError(tok, false, true);
}
}
}
}
void CheckConditionImpl::clarifyConditionError(const Token *tok, bool assign, bool boolop)
{
std::string errmsg;
if (assign)
errmsg = "Suspicious condition (assignment + comparison); Clarify expression with parentheses.";
else if (boolop)
errmsg = "Boolean result is used in bitwise operation. Clarify expression with parentheses.\n"
"Suspicious expression. Boolean result is used in bitwise operation. The operator '!' "
"and the comparison operators have higher precedence than bitwise operators. "
"It is recommended that the expression is clarified with parentheses.";
else
errmsg = "Suspicious condition (bitwise operator + comparison); Clarify expression with parentheses.\n"
"Suspicious condition. Comparison operators have higher precedence than bitwise operators. "
"Please clarify the condition with parentheses.";
reportError(tok,
Severity::style,
"clarifyCondition",
errmsg, CWE398, Certainty::normal);
}
void CheckConditionImpl::alwaysTrueFalse()
{
const bool pedantic = mSettings.isPremiumEnabled("alwaysTrue") ||
mSettings.isPremiumEnabled("alwaysFalse") ||
mSettings.isPremiumEnabled("knownConditionTrueFalse");
if (!pedantic && !mSettings.severity.isEnabled(Severity::style))
return;
logChecker("CheckCondition::alwaysTrueFalse"); // style
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
// don't write false positives when templates are used or inside of asserts or non-evaluated contexts
if (tok->link() && (Token::simpleMatch(tok, ">|" || cmp == ">=");
else
result = (cmp == "' && i == 0)
// num > var
result = (kiv > 0);
else if (tok->str()[0] == '>' && i == 1)
// var > num
result = (kiv < 0);
else if (tok->str()[0] == '