/*
* 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 .
*/
//---------------------------------------------------------------------------
#include "checkother.h"
#include "astutils.h"
#include "fwdanalysis.h"
#include "library.h"
#include "mathlib.h"
#include "platform.h"
#include "settings.h"
#include "standards.h"
#include "symboldatabase.h"
#include "token.h"
#include "tokenize.h"
#include "tokenlist.h"
#include "utils.h"
#include "valueflow.h"
#include "vfvalue.h"
#include // find_if()
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//---------------------------------------------------------------------------
static const CWE CWE128(128U); // Wrap-around Error
static const CWE CWE131(131U); // Incorrect Calculation of Buffer Size
static const CWE CWE197(197U); // Numeric Truncation Error
static const CWE CWE362(362U); // Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
static const CWE CWE369(369U); // Divide By Zero
static const CWE CWE398(398U); // Indicator of Poor Code Quality
static const CWE CWE475(475U); // Undefined Behavior for Input to API
static const CWE CWE561(561U); // Dead Code
static const CWE CWE563(563U); // Assignment to Variable without Use ('Unused Variable')
static const CWE CWE570(570U); // Expression is Always False
static const CWE CWE571(571U); // Expression is Always True
static const CWE CWE672(672U); // Operation on a Resource after Expiration or Release
static const CWE CWE628(628U); // Function Call with Incorrectly Specified Arguments
static const CWE CWE683(683U); // Function Call With Incorrect Order of Arguments
static const CWE CWE704(704U); // Incorrect Type Conversion or Cast
static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior
static const CWE CWE768(768U); // Incorrect Short Circuit Evaluation
static const CWE CWE783(783U); // Operator Precedence Logic Error
//----------------------------------------------------------------------------------
// The return value of fgetc(), getc(), ungetc(), getchar() etc. is an integer value.
// If this return value is stored in a character variable and then compared
// to EOF, which is an integer, the comparison maybe be false.
//
// Reference:
// - Ticket #160
// - http://www.cplusplus.com/reference/cstdio/fgetc/
// - http://www.cplusplus.com/reference/cstdio/getc/
// - http://www.cplusplus.com/reference/cstdio/getchar/
// - http://www.cplusplus.com/reference/cstdio/ungetc/ ...
//----------------------------------------------------------------------------------
void CheckOtherImpl::checkCastIntToCharAndBack()
{
if (!mSettings.severity.isEnabled(Severity::warning))
return;
logChecker("CheckOther::checkCastIntToCharAndBack"); // warning
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
std::map vars;
for (const Token* tok = scope->bodyStart->next(); tok && tok != scope->bodyEnd; tok = tok->next()) {
// Quick check to see if any of the matches below have any chances
if (!Token::Match(tok, "%var%|EOF %comp%|="))
continue;
if (Token::Match(tok, "%var% = fclose|fflush|fputc|fputs|fscanf|getchar|getc|fgetc|putchar|putc|puts|scanf|sscanf|ungetc (")) {
const Variable *var = tok->variable();
if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) {
vars[tok->varId()] = tok->strAt(2);
}
} else if (Token::Match(tok, "EOF %comp% ( %var% = fclose|fflush|fputc|fputs|fscanf|getchar|getc|fgetc|putchar|putc|puts|scanf|sscanf|ungetc (")) {
tok = tok->tokAt(3);
const Variable *var = tok->variable();
if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) {
checkCastIntToCharAndBackError(tok, tok->strAt(2));
}
} else if (tok->isCpp() && (Token::Match(tok, "EOF %comp% ( %var% = std :: cin . get (") || Token::Match(tok, "EOF %comp% ( %var% = cin . get ("))) {
tok = tok->tokAt(3);
const Variable *var = tok->variable();
if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) {
checkCastIntToCharAndBackError(tok, "cin.get");
}
} else if (tok->isCpp() && (Token::Match(tok, "%var% = std :: cin . get (") || Token::Match(tok, "%var% = cin . get ("))) {
const Variable *var = tok->variable();
if (var && var->typeEndToken()->str() == "char" && !var->typeEndToken()->isSigned()) {
vars[tok->varId()] = "cin.get";
}
} else if (Token::Match(tok, "%var% %comp% EOF")) {
if (vars.find(tok->varId()) != vars.end()) {
checkCastIntToCharAndBackError(tok, vars[tok->varId()]);
}
} else if (Token::Match(tok, "EOF %comp% %var%")) {
tok = tok->tokAt(2);
if (vars.find(tok->varId()) != vars.end()) {
checkCastIntToCharAndBackError(tok, vars[tok->varId()]);
}
}
}
}
}
void CheckOtherImpl::checkCastIntToCharAndBackError(const Token *tok, const std::string &strFunctionName)
{
reportError(
tok,
Severity::warning,
"checkCastIntToCharAndBack",
"$symbol:" + strFunctionName + "\n"
"Storing $symbol() return value in char variable and then comparing with EOF.\n"
"When saving $symbol() return value in char variable there is loss of precision. "
" When $symbol() returns EOF this value is truncated. Comparing the char "
"variable with EOF can have unexpected results. For instance a loop \"while (EOF != (c = $symbol());\" "
"loops forever on some compilers/platforms and on other compilers/platforms it will stop "
"when the file contains a matching character.", CWE197, Certainty::normal
);
}
//---------------------------------------------------------------------------
// Clarify calculation precedence for ternary operators.
//---------------------------------------------------------------------------
void CheckOtherImpl::clarifyCalculation()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("clarifyCalculation"))
return;
logChecker("CheckOther::clarifyCalculation"); // 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()) {
// ? operator where lhs is arithmetical expression
if (tok->str() != "?" || !tok->astOperand1() || !tok->astOperand1()->isCalculation())
continue;
if (!tok->astOperand1()->isArithmeticalOp() && tok->astOperand1()->tokType() != Token::eBitOp)
continue;
// non-pointer calculation in lhs and pointer in rhs => no clarification is needed
if (tok->astOperand1()->isBinaryOp() && Token::Match(tok->astOperand1(), "%or%|&|%|*|/") && tok->astOperand2()->valueType() && tok->astOperand2()->valueType()->pointer > 0)
continue;
// bit operation in lhs and char literals in rhs => probably no mistake
if (tok->astOperand1()->tokType() == Token::eBitOp && Token::Match(tok->astOperand2()->astOperand1(), "%char%") && Token::Match(tok->astOperand2()->astOperand2(), "%char%"))
continue;
// 2nd operand in lhs has known integer value => probably no mistake
if (tok->astOperand1()->isBinaryOp() && tok->astOperand1()->astOperand2()->hasKnownIntValue()) {
const Token *op = tok->astOperand1()->astOperand2();
if (op->isNumber())
continue;
if (op->valueType() && op->valueType()->isEnum())
continue;
}
// Is code clarified by parentheses already?
const Token *tok2 = tok->astOperand1();
for (; tok2; tok2 = tok2->next()) {
if (tok2->str() == "(")
tok2 = tok2->link();
else if (tok2->str() == ")")
break;
else if (tok2->str() == "?") {
clarifyCalculationError(tok, tok->astOperand1()->str());
break;
}
}
}
}
}
void CheckOtherImpl::clarifyCalculationError(const Token *tok, const std::string &op)
{
// suspicious calculation
const std::string calc("'a" + op + "b?c:d'");
// recommended calculation #1
const std::string s1("'(a" + op + "b)?c:d'");
// recommended calculation #2
const std::string s2("'a" + op + "(b?c:d)'");
reportError(tok,
Severity::style,
"clarifyCalculation",
"Clarify calculation precedence for '" + op + "' and '?'.\n"
"Suspicious calculation. Please use parentheses to clarify the code. "
"The code '" + calc + "' should be written as either '" + s1 + "' or '" + s2 + "'.", CWE783, Certainty::normal);
}
//---------------------------------------------------------------------------
// Clarify (meaningless) statements like *foo++; with parentheses.
//---------------------------------------------------------------------------
void CheckOtherImpl::clarifyStatement()
{
if (!mSettings.severity.isEnabled(Severity::warning))
return;
logChecker("CheckOther::clarifyStatement"); // warning
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token* tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) {
if (tok->astOperand1() && Token::Match(tok, "* %name%")) {
const Token *tok2 = tok->previous();
while (tok2 && tok2->str() == "*")
tok2 = tok2->previous();
if (tok2 && !tok2->astParent() && Token::Match(tok2, "[{};]")) {
tok2 = tok->astOperand1();
if (Token::Match(tok2, "++|-- [;,]"))
clarifyStatementError(tok2);
}
}
}
}
}
void CheckOtherImpl::clarifyStatementError(const Token *tok)
{
reportError(tok, Severity::warning, "clarifyStatement", "In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n"
"A statement like '*A++;' might not do what you intended. Postfix 'operator++' is executed before 'operator*'. "
"Thus, the dereference is meaningless. Did you intend to write '(*A)++;'?", CWE783, Certainty::normal);
}
//---------------------------------------------------------------------------
// Check for suspicious occurrences of 'if(); {}'.
//---------------------------------------------------------------------------
void CheckOtherImpl::checkSuspiciousSemicolon()
{
if (!mSettings.certainty.isEnabled(Certainty::inconclusive) || !mSettings.severity.isEnabled(Severity::warning))
return;
const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
logChecker("CheckOther::checkSuspiciousSemicolon"); // warning,inconclusive
// Look for "if(); {}", "for(); {}" or "while(); {}"
for (const Scope &scope : symbolDatabase->scopeList) {
if (scope.type == ScopeType::eIf || scope.type == ScopeType::eElse || scope.type == ScopeType::eWhile || scope.type == ScopeType::eFor) {
// Ensure the semicolon is at the same line number as the if/for/while statement
// and the {..} block follows it without an extra empty line.
if (Token::simpleMatch(scope.bodyStart, "{ ; } {") &&
scope.bodyStart->previous()->linenr() == scope.bodyStart->tokAt(2)->linenr() &&
scope.bodyStart->linenr()+1 >= scope.bodyStart->tokAt(3)->linenr() &&
!scope.bodyStart->tokAt(3)->isExpandedMacro()) {
suspiciousSemicolonError(scope.classDef);
}
}
}
}
void CheckOtherImpl::suspiciousSemicolonError(const Token* tok)
{
reportError(tok, Severity::warning, "suspiciousSemicolon",
"Suspicious use of ; at the end of '" + (tok ? tok->str() : std::string()) + "' statement.", CWE398, Certainty::normal);
}
/** @brief would it make sense to use dynamic_cast instead of C style cast? */
static bool isDangerousTypeConversion(const Token* const tok)
{
const Token* from = tok->astOperand1();
if (!from)
return false;
if (!tok->valueType() || !from->valueType())
return false;
if (tok->valueType()->typeScope != nullptr &&
tok->valueType()->typeScope == from->valueType()->typeScope)
return false;
if (tok->valueType()->type == from->valueType()->type &&
tok->valueType()->isPrimitive())
return false;
// cast from derived object to base object is safe..
if (tok->valueType()->typeScope && from->valueType()->typeScope) {
const Type* fromType = from->valueType()->typeScope->definedType;
const Type* toType = tok->valueType()->typeScope->definedType;
if (fromType && toType && fromType->isDerivedFrom(toType->name()))
return false;
}
const bool refcast = (tok->valueType()->reference != Reference::None);
if (!refcast && tok->valueType()->pointer == 0)
return false;
if (!refcast && from->valueType()->pointer == 0)
return false;
if (tok->valueType()->type == ValueType::Type::VOID || from->valueType()->type == ValueType::Type::VOID)
return false;
if (tok->valueType()->pointer == 0 && tok->valueType()->isIntegral())
// ok: (uintptr_t)ptr;
return false;
if (from->valueType()->pointer == 0 && from->valueType()->isIntegral())
// ok: (int *)addr;
return false;
return true;
}
//---------------------------------------------------------------------------
// For C++ code, warn if C-style casts are used on pointer types
//---------------------------------------------------------------------------
void CheckOtherImpl::warningOldStylePointerCast()
{
// Only valid on C++ code
if (!mTokenizer->isCPP())
return;
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("cstyleCast"))
return;
logChecker("CheckOther::warningOldStylePointerCast"); // style,c++
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
const Token* tok;
if (scope->function && scope->function->isConstructor())
tok = scope->classDef;
else
tok = scope->bodyStart;
for (; tok && tok != scope->bodyEnd; tok = tok->next()) {
// Old style pointer casting..
if (!tok->isCast() || tok->isBinaryOp())
continue;
if (isDangerousTypeConversion(tok))
continue;
const Token* const errtok = tok;
const Token* castTok = tok->next();
while (Token::Match(castTok, "const|volatile|class|struct|union|%type%|::")) {
castTok = castTok->next();
if (Token::simpleMatch(castTok, "link() && tokIter; tokIter = tokIter->next()) {
if (Token::Match(tokIter, "[;{}] %any% :") && labelName->str() == tokIter->strAt(1)) {
labelInFollowingLoop = true;
break;
}
}
}
}
// hide FP for statements that just hide compiler warnings about unused function arguments
bool silencedCompilerWarningOnly = false;
const Token *silencedWarning = secondBreak;
for (;;) {
if (Token::Match(silencedWarning, "( void ) %name% ;")) {
silencedWarning = silencedWarning->tokAt(5);
continue;
}
if (silencedWarning && silencedWarning == scope->bodyEnd)
silencedCompilerWarningOnly = true;
break;
}
if (silencedWarning)
secondBreak = silencedWarning;
if (!labelInFollowingLoop && !silencedCompilerWarningOnly && !isVardeclInSwitch(secondBreak))
unreachableCodeError(secondBreak, tok, inconclusive);
tok = Token::findmatch(secondBreak, "[}:]");
} else if (secondBreak->scope() && secondBreak->scope()->isLoopScope() && secondBreak->str() == "}" && tok->str() == "continue") {
redundantContinueError(tok);
tok = secondBreak;
} else
tok = secondBreak;
if (!tok)
break;
tok = tok->previous(); // Will be advanced again by for loop
}
}
}
}
void CheckOtherImpl::duplicateBreakError(const Token *tok, bool inconclusive)
{
reportError(tok, Severity::style, "duplicateBreak",
"Consecutive return, break, continue, goto or throw statements are unnecessary.\n"
"Consecutive return, break, continue, goto or throw statements are unnecessary. "
"The second statement can never be executed, and so should be removed.", CWE561, inconclusive ? Certainty::inconclusive : Certainty::normal);
}
void CheckOtherImpl::unreachableCodeError(const Token *tok, const Token* noreturn, bool inconclusive)
{
std::string msg = "Statements following ";
if (noreturn && (noreturn->function() || mSettings.library.isnoreturn(noreturn)))
msg += "noreturn function '" + noreturn->str() + "()'";
else if (noreturn && noreturn->isKeyword())
msg += "'" + noreturn->str() + "'";
else
msg += "return, break, continue, goto or throw";
msg += " will never be executed.";
reportError(tok, Severity::style, "unreachableCode",
msg, CWE561, inconclusive ? Certainty::inconclusive : Certainty::normal);
}
void CheckOtherImpl::redundantContinueError(const Token *tok)
{
reportError(tok, Severity::style, "redundantContinue",
"'continue' is redundant since it is the last statement in a loop.", CWE561, Certainty::normal);
}
static bool isSimpleExpr(const Token* tok, const Variable* var, const Settings& settings) {
if (!tok)
return false;
if (tok->isNumber() || tok->tokType() == Token::eString || tok->tokType() == Token::eChar || tok->isBoolean())
return true;
if (isNullOperand(tok))
return true;
bool needsCheck = tok->varId() > 0;
if (!needsCheck) {
if (tok->isArithmeticalOp())
return isSimpleExpr(tok->astOperand1(), var, settings) && (!tok->astOperand2() || isSimpleExpr(tok->astOperand2(), var, settings));
const Token* ftok = tok->previous();
if (Token::Match(ftok, "%name% (") &&
((ftok->function() && ftok->function()->isConst()) || settings.library.isFunctionConst(ftok->str(), /*pure*/ true)))
needsCheck = true;
else if (tok->str() == "[") {
needsCheck = tok->astOperand1() && tok->astOperand1()->varId() > 0;
tok = tok->astOperand1();
}
else if (isLeafDot(tok->astOperand2())) {
needsCheck = tok->astOperand2()->varId() > 0;
tok = tok->astOperand2();
}
}
return (needsCheck && !findExpressionChanged(tok, tok->astParent(), var->scope()->bodyEnd, settings));
}
//---------------------------------------------------------------------------
// Check scope of variables..
//---------------------------------------------------------------------------
void CheckOtherImpl::checkVariableScope()
{
if (mSettings.clang)
return;
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("variableScope"))
return;
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
// In C it is common practice to declare local variables at the
// start of functions.
if (mSettings.daca && mTokenizer->isC())
return;
logChecker("CheckOther::checkVariableScope"); // style,notclang
for (const Variable* var : symbolDatabase->variableList()) {
if (!var || !var->isLocal() || var->isConst())
continue;
if (var->nameToken()->isExpandedMacro())
continue;
if (isStructuredBindingVariable(var) && // warn for single decomposition
!(Token::simpleMatch(var->nameToken()->astParent(), "[") && var->nameToken()->astParent()->astOperand2() == var->nameToken()))
continue;
const bool isPtrOrRef = var->isPointer() || var->isReference();
const bool isSimpleType = var->typeStartToken()->isStandardType() || var->typeStartToken()->isEnumType() || var->typeStartToken()->isC() || symbolDatabase->isRecordTypeWithoutSideEffects(var->type());
if (!isPtrOrRef && !isSimpleType && !astIsContainer(var->nameToken()))
continue;
if (mTokenizer->hasIfdef(var->nameToken(), var->scope()->bodyEnd))
continue;
// reference of range for loop variable..
if (Token::Match(var->nameToken()->previous(), "& %var% = %var% .")) {
const Token *otherVarToken = var->nameToken()->tokAt(2);
const Variable *otherVar = otherVarToken->variable();
if (otherVar && Token::Match(otherVar->nameToken(), "%var% :") &&
otherVar->nameToken()->next()->astParent() &&
Token::simpleMatch(otherVar->nameToken()->next()->astParent()->previous(), "for ("))
continue;
}
bool forHead = false; // Don't check variables declared in header of a for loop
for (const Token* tok = var->typeStartToken(); tok; tok = tok->previous()) {
if (tok->str() == "(") {
forHead = true;
break;
}
if (Token::Match(tok, "[;{}]"))
break;
}
if (forHead)
continue;
const Token* tok = var->nameToken()->next();
bool isConstructor = false;
if (Token::Match(tok, "; %varid% =", var->declarationId())) { // bailout for assignment
tok = tok->tokAt(2)->astOperand2();
if (!isSimpleExpr(tok, var, mSettings))
continue;
}
else if (Token::Match(tok, "{|(")) { // bailout for constructor
isConstructor = true;
const Token* argTok = tok->astOperand2();
bool bail = false;
while (argTok) {
if (Token::simpleMatch(argTok, ",")) {
if (!isSimpleExpr(argTok->astOperand2(), var, mSettings)) {
bail = true;
break;
}
} else if (argTok->str() != "." && !isSimpleExpr(argTok, var, mSettings)) {
bail = true;
break;
}
argTok = argTok->astOperand1();
}
if (bail)
continue;
}
// bailout if initialized with function call that has possible side effects
if (!isConstructor && Token::Match(tok, "[(=]") && Token::simpleMatch(tok->astOperand2(), "("))
continue;
bool reduce = true;
bool used = false; // Don't warn about unused variables
for (; tok && tok != var->scope()->bodyEnd; tok = tok->next()) {
if (tok->str() == "{" && tok->scope() != tok->previous()->scope() && !tok->isExpandedMacro() && !isWithinScope(tok, var, ScopeType::eLambda)) {
if (used) {
bool used2 = false;
if (!checkInnerScope(tok, var, used2) || used2) {
reduce = false;
break;
}
} else if (!checkInnerScope(tok, var, used)) {
reduce = false;
break;
}
tok = tok->link();
// parse else if blocks..
} else if (Token::simpleMatch(tok, "else { if (") && tok->next()->isInsertedBrace() && Token::simpleMatch(tok->linkAt(3), ") {")) {
tok = tok->next();
} else if (tok->varId() == var->declarationId() || tok->str() == "goto") {
reduce = false;
break;
}
}
if (reduce && used)
variableScopeError(var->nameToken(), var->name());
}
}
// used to check if an argument to a function might depend on another argument
static bool mayDependOn(const ValueType *other, const ValueType *original)
{
if (!other || !original)
return false;
// other must be pointer
if (!other->pointer)
return false;
// must be same underlying type
if (other->type != original->type)
return false;
const int otherPtr = other->pointer + (other->reference == Reference::LValue ? 1 : 0);
const int originalPtr = original->pointer;
if (otherPtr == originalPtr) {
// if other is not const than original may be copied to other
return !other->isConst(otherPtr);
}
// other may be reassigned to original
return otherPtr > originalPtr;
}
static bool isOnlyUsedInCurrentScope(const Variable* var, const Token *tok, const Scope* scope)
{
if (tok->scope() == scope)
return true;
if (tok->scope()->type == ScopeType::eSwitch)
return false;
return !Token::findmatch(tok->scope()->bodyEnd, "%varid%", var->scope()->bodyEnd, var->declarationId());
}
bool CheckOtherImpl::checkInnerScope(const Token *tok, const Variable* var, bool& used) const
{
const Scope* scope = tok->next()->scope();
bool loopVariable = scope->isLoopScope();
bool noContinue = true;
const Token* forHeadEnd = nullptr;
const Token* end = tok->link();
if (scope->type == ScopeType::eUnconditional && (tok->strAt(-1) == ")" || tok->previous()->isName())) // Might be an unknown macro like BOOST_FOREACH
loopVariable = true;
if (scope->type == ScopeType::eDo) {
end = end->linkAt(2);
} else if (loopVariable && tok->strAt(-1) == ")") {
tok = tok->linkAt(-1); // Jump to opening ( of for/while statement
} else if (scope->type == ScopeType::eSwitch) {
for (const Scope* innerScope : scope->nestedList) {
if (used) {
bool used2 = false;
if (!checkInnerScope(innerScope->bodyStart, var, used2) || used2) {
return false;
}
} else if (!checkInnerScope(innerScope->bodyStart, var, used)) {
return false;
}
}
}
bool bFirstAssignment=false;
for (; tok && tok != end; tok = tok->next()) {
if (tok->str() == "goto")
return false;
if (tok->str() == "continue")
noContinue = false;
if (Token::simpleMatch(tok, "for ("))
forHeadEnd = tok->linkAt(1);
if (tok == forHeadEnd)
forHeadEnd = nullptr;
if (loopVariable && noContinue && !forHeadEnd && scope->type != ScopeType::eSwitch && Token::Match(tok, "%varid% =", var->declarationId()) &&
isOnlyUsedInCurrentScope(var, tok, scope)) { // Assigned in outer scope.
loopVariable = false;
std::pair range = tok->next()->findExpressionStartEndTokens();
if (range.first)
range.first = range.first->next();
const Token* exprTok = findExpression(var->nameToken()->exprId(), range.first, range.second, [&](const Token* tok2) {
return tok2->varId() == var->declarationId();
});
if (exprTok) {
tok = exprTok;
loopVariable = true;
}
}
if (loopVariable && Token::Match(tok, "%varid% !!=", var->declarationId())) // Variable used in loop
return false;
if (Token::Match(tok, "& %varid%", var->declarationId())) // Taking address of variable
return false;
if (Token::Match(tok, "%varid% =", var->declarationId())) {
if (!bFirstAssignment && var->isInit() && Token::findmatch(tok->tokAt(2), "%varid%", Token::findsimplematch(tok->tokAt(3), ";"), var->declarationId()))
return false;
bFirstAssignment = true;
}
if (!bFirstAssignment && Token::Match(tok, "* %varid%", var->declarationId())) // dereferencing means access to previous content
return false;
if (Token::Match(tok, "= %varid%", var->declarationId()) && (var->isArray() || var->isPointer() || (var->valueType() && var->valueType()->container))) // Create a copy of array/pointer. Bailout, because the memory it points to might be necessary in outer scope
return false;
if (tok->varId() == var->declarationId()) {
used = true;
if (scope == tok->scope()) {
if (scope->type == ScopeType::eSwitch)
return false; // Used in outer switch scope - unsafe or impossible to reduce scope
if (scope->bodyStart && scope->bodyStart->isSimplifiedIfInitStmt())
return false; // simplified if/for/switch init statement
}
if (var->isArrayOrPointer()) {
int argn{};
if (const Token* ftok = getTokenArgumentFunction(tok, argn)) { // var passed to function?
if (ftok->next()->astParent()) { // return value used?
if (ftok->function() && Function::returnsPointer(ftok->function()))
return false;
const std::string ret = mSettings.library.returnValueType(ftok); // assume that var is returned
if (!ret.empty() && ret.back() == '*')
return false;
}
if (ftok->function()) {
const std::list &argvars = ftok->function()->argumentList;
if (const Variable* argvar = ftok->function()->getArgumentVar(argn)) {
if (!std::all_of(argvars.cbegin(), argvars.cend(), [&](const Variable& other) {
return &other == argvar || !mayDependOn(other.valueType(), argvar->valueType());
}))
return false;
}
}
}
}
const auto yield = astContainerYield(tok, mSettings.library);
if (yield == Library::Container::Yield::BUFFER || yield == Library::Container::Yield::BUFFER_NT)
return false;
}
}
return true;
}
void CheckOtherImpl::variableScopeError(const Token *tok, const std::string &varname)
{
reportError(tok,
Severity::style,
"variableScope",
"$symbol:" + varname + "\n"
"The scope of the variable '$symbol' can be reduced.\n"
"The scope of the variable '$symbol' can be reduced. Warning: Be careful "
"when fixing this message, especially when there are inner loops. Here is an "
"example where cppcheck will write that the scope for 'i' can be reduced:\n"
"void f(int x)\n"
"{\n"
" int i = 0;\n"
" if (x) {\n"
" // it's safe to move 'int i = 0;' here\n"
" for (int n = 0; n < 10; ++n) {\n"
" // it is possible but not safe to move 'int i = 0;' here\n"
" do_something(&i);\n"
" }\n"
" }\n"
"}\n"
"When you see this message it is always safe to reduce the variable scope 1 level.", CWE398, Certainty::normal);
}
//---------------------------------------------------------------------------
// Comma in return statement: return a+1, b++;. (experimental)
//---------------------------------------------------------------------------
void CheckOtherImpl::checkCommaSeparatedReturn()
{
// TODO: This is experimental for now. See #5076
if ((true) || !mSettings.severity.isEnabled(Severity::style)) // NOLINT(readability-simplify-boolean-expr,readability-redundant-parentheses)
return;
// logChecker
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (tok->str() == "return") {
tok = tok->next();
while (tok && tok->str() != ";") {
if (tok->link() && Token::Match(tok, "[([{"))
return false;
return isWithoutSideEffects(tok) && isConstStatement(tok->astOperand2(), library, platformIndependent);
}
if (tok->isCast() && tok->next() && tok->next()->isStandardType())
return isWithoutSideEffects(tok->astOperand1()) && isConstStatement(tok->astOperand1(), library, platformIndependent);
if (Token::simpleMatch(tok, "."))
return isConstStatement(tok->astOperand2(), library, platformIndependent);
if (Token::simpleMatch(tok, ",")) {
if (tok->astParent()) // warn about const statement on rhs at the top level
return isConstStatement(tok->astOperand1(), library, platformIndependent) &&
isConstStatement(tok->astOperand2(), library, platformIndependent);
const Token* lml = previousBeforeAstLeftmostLeaf(tok); // don't warn about matrix/vector assignment (e.g. Eigen)
if (lml)
lml = lml->next();
const Token* stream = lml;
while (stream && Token::Match(stream->astParent(), ".|[|(|*"))
stream = stream->astParent();
return (!stream || !isLikelyStream(stream)) && isConstStatement(tok->astOperand2(), library, platformIndependent);
}
if (Token::simpleMatch(tok, "?") && Token::simpleMatch(tok->astOperand2(), ":")) // ternary operator
return isConstStatement(tok->astOperand1(), library, platformIndependent) &&
isConstStatement(tok->astOperand2()->astOperand1(), library, platformIndependent) &&
isConstStatement(tok->astOperand2()->astOperand2(), library, platformIndependent);
if (isBracketAccess(tok) && isWithoutSideEffects(tok->astOperand1(), /*checkArrayAccess*/ true, /*checkReference*/ false)) {
const bool isChained = succeeds(tok->astParent(), tok);
if (Token::simpleMatch(tok->astParent(), "[")) {
if (isChained)
return isConstStatement(tok->astOperand2(), library, platformIndependent) &&
isConstStatement(tok->astParent(), library, platformIndependent);
return isNestedBracket && isConstStatement(tok->astOperand2(), library, platformIndependent);
}
return isConstStatement(tok->astOperand2(), library, platformIndependent, /*isNestedBracket*/ !isChained);
}
if (!tok->astParent() && findLambdaEndToken(tok))
return true;
tok2 = tok;
if (tok2->str() == "::")
tok2 = tok2->next();
if (Token::Match(tok2, "%name% ;")) {
if (tok2->function())
return true;
std::string funcStr = tok2->str();
while (tok2->index() > 1 && Token::Match(tok2->tokAt(-2), "%name% ::")) {
funcStr.insert(0, tok2->strAt(-2) + "::");
tok2 = tok2->tokAt(-2);
}
if (library.functions().count(funcStr) > 0)
return true;
}
return false;
}
static bool isVoidStmt(const Token *tok)
{
if (Token::simpleMatch(tok, "( void") && !(tok->astOperand1() && (tok->astOperand1()->isLiteral() || isNullOperand(tok->astOperand1()))))
return true;
if (isCPPCast(tok) && tok->astOperand1() && Token::Match(tok->astOperand1()->next(), "< void *| >") &&
!(tok->astOperand2() && (tok->astOperand2()->isLiteral() || isNullOperand(tok->astOperand2()))))
return true;
return false;
}
static bool isConstTop(const Token *tok)
{
if (!tok)
return false;
if (!tok->astParent())
return true;
if (Token::simpleMatch(tok->astParent(), ";") &&
Token::Match(tok->astTop()->previous(), "for|if (") && Token::simpleMatch(tok->astTop()->astOperand2(), ";")) {
if (Token::simpleMatch(tok->astParent()->astParent(), ";"))
return tok->astParent()->astOperand2() == tok;
return tok->astParent()->astOperand1() == tok;
}
if (Token::simpleMatch(tok, "[")) {
const Token* bracTok = tok;
while (Token::simpleMatch(bracTok->astParent(), "["))
bracTok = bracTok->astParent();
if (!bracTok->astParent())
return true;
}
if (tok->str() == "," && tok->astParent()->isAssignmentOp())
return true;
return false;
}
void CheckOtherImpl::checkIncompleteStatement()
{
if (!mSettings.severity.isEnabled(Severity::warning) &&
!mSettings.isPremiumEnabled("constStatement"))
return;
logChecker("CheckOther::checkIncompleteStatement"); // warning
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
const Scope *scope = tok->scope();
if (scope && !scope->isExecutable())
continue;
if (!isConstTop(tok))
continue;
if (tok->str() == "," && Token::simpleMatch(tok->astTop()->previous(), "for ("))
continue;
// Do not warn for statement when both lhs and rhs has side effects:
// dostuff() || x=213;
if (Token::Match(tok, "%oror%|&&")) {
bool warn = false;
visitAstNodes(tok, [&warn](const Token *child) {
if (Token::Match(child, "%oror%|&&"))
return ChildrenToVisit::op1_and_op2;
if (child->isAssignmentOp())
return ChildrenToVisit::none;
if (child->tokType() == Token::Type::eIncDecOp)
return ChildrenToVisit::none;
if (Token::Match(child->previous(), "%name% ("))
return ChildrenToVisit::none;
warn = true;
return ChildrenToVisit::done;
});
if (!warn)
continue;
}
const Token *rtok = nextAfterAstRightmostLeaf(tok);
if (!Token::simpleMatch(tok->astParent(), ";") && !Token::simpleMatch(rtok, ";") &&
!Token::Match(tok->previous(), ";|}|{ %any% ;") &&
!(tok->isCpp() && tok->isCast() && !tok->astParent()) &&
!(!tok->astParent() && tok->astOperand1() && Token::Match(tok->previous(), "%name% (") && tok->previous()->isKeyword()) &&
!Token::simpleMatch(tok->tokAt(-2), "for (") &&
!Token::Match(tok->tokAt(-1), "%var% [") &&
!(tok->str() == "," && tok->astParent() && tok->astParent()->isAssignmentOp()))
continue;
// Skip statement expressions
if (Token::simpleMatch(rtok, "; } )") || Token::simpleMatch(tok->next(), "; } )"))
continue;
if (!isConstStatement(tok, mSettings.library, false))
continue;
if (isVoidStmt(tok))
continue;
if (tok->isCpp() && tok->str() == "&" && !(tok->astOperand1() && tok->astOperand1()->valueType() && tok->astOperand1()->valueType()->isIntegral()))
// Possible archive
continue;
const bool inconclusive = tok->isConstOp() && !mSettings.isPremiumEnabled("constStatement");
if (mSettings.certainty.isEnabled(Certainty::inconclusive) || !inconclusive)
constStatementError(tok, tok->isNumber() ? "numeric" : "string", inconclusive);
}
}
void CheckOtherImpl::constStatementError(const Token *tok, const std::string &type, bool inconclusive)
{
std::string msg;
if (Token::simpleMatch(tok, "=="))
msg = "Found suspicious equality comparison. Did you intend to assign a value instead?";
else if (Token::Match(tok, ",|!|~|%cop%"))
msg = "Found suspicious operator '" + tok->str() + "', result is not used.";
else if (Token::Match(tok, "%var%"))
msg = "Unused variable value '" + tok->str() + "'";
else if (isConstant(tok)) {
std::string typeStr("string");
if (tok->isNumber())
typeStr = "numeric";
else if (tok->isBoolean())
typeStr = "bool";
else if (tok->tokType() == Token::eChar)
typeStr = "character";
else if (isNullOperand(tok))
typeStr = "null";
else if (tok->isEnumerator())
typeStr = "enumerator";
msg = "Redundant code: Found a statement that begins with " + typeStr + " constant.";
}
else if (!tok)
msg = "Redundant code: Found a statement that begins with " + type + " constant.";
else if (tok->isCast() && tok->tokType() == Token::Type::eExtendedOp)
msg = "Redundant code: Found unused cast in expression '" + tok->expressionString() + "'.";
else if (tok->str() == "?" && tok->tokType() == Token::Type::eExtendedOp)
msg = "Redundant code: Found unused result of ternary operator.";
else if (tok->str() == "." && tok->tokType() == Token::Type::eOther)
msg = "Redundant code: Found unused member access.";
else if (tok->str() == "[" && tok->tokType() == Token::Type::eExtendedOp)
msg = "Redundant code: Found unused array access.";
else if (tok->str() == "[" && !tok->astParent())
msg = "Redundant code: Found unused lambda.";
else if (Token::Match(tok, "%name%|::"))
msg = "Redundant code: Found unused function.";
else if (Token::Match(tok->previous(), "%name% ("))
msg = "Redundant code: Found unused '" + tok->strAt(-1) + "' expression.";
else if (mSettings.debugwarnings) {
reportError(tok, Severity::debug, "debug", "constStatementError not handled.");
return;
}
reportError(tok, Severity::warning, "constStatement", msg, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal);
}
//---------------------------------------------------------------------------
// Detect division by zero.
//---------------------------------------------------------------------------
void CheckOtherImpl::checkZeroDivision()
{
logChecker("CheckOther::checkZeroDivision");
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (!tok->astOperand2() || !tok->astOperand1())
continue;
if (tok->str() != "%" && tok->str() != "/" && tok->str() != "%=" && tok->str() != "/=")
continue;
if (!tok->valueType() || !tok->valueType()->isIntegral())
continue;
if (tok->scope() && tok->scope()->type == ScopeType::eEnum) // don't warn for compile-time error
continue;
// Value flow..
const ValueFlow::Value *value = tok->astOperand2()->getValue(0LL);
if (value && mSettings.isEnabled(value, false))
zerodivError(tok, value);
}
}
void CheckOtherImpl::zerodivError(const Token *tok, const ValueFlow::Value *value)
{
if (!tok && !value) {
reportError(tok, Severity::error, "zerodiv", "Division by zero.", CWE369, Certainty::normal);
reportError(tok, Severity::warning, "zerodivcond", ValueFlow::eitherTheConditionIsRedundant(nullptr) + " or there is division by zero.", CWE369, Certainty::normal);
return;
}
ErrorPath errorPath = getErrorPath(tok, value, "Division by zero");
std::ostringstream errmsg;
if (value->condition) {
const int line = tok ? tok->linenr() : 0;
errmsg condition)
tokens(); tok; tok = tok->next()) {
if (tok->str() != "/")
continue;
if (!Token::Match(tok->astParent(), "[+-]"))
continue;
if (Token::simpleMatch(tok->astOperand2(), "0.0"))
nanInArithmeticExpressionError(tok);
}
}
void CheckOtherImpl::nanInArithmeticExpressionError(const Token *tok)
{
reportError(tok, Severity::style, "nanInArithmeticExpression",
"Using NaN/Inf in a computation.\n"
"Using NaN/Inf in a computation. "
"Although nothing bad really happens, it is suspicious.", CWE369, Certainty::normal);
}
//---------------------------------------------------------------------------
// Creating instance of classes which are destroyed immediately
//---------------------------------------------------------------------------
void CheckOtherImpl::checkMisusedScopedObject()
{
// Skip this check for .c files
if (mTokenizer->isC())
return;
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedScopedObject"))
return;
logChecker("CheckOther::checkMisusedScopedObject"); // style,c++
auto getConstructorTok = [](const Token* tok, std::string& typeStr) -> const Token* {
if (!Token::Match(tok, "[;{}] %name%") || tok->next()->isKeyword())
return nullptr;
tok = tok->next();
typeStr.clear();
while (Token::Match(tok, "%name% ::")) {
typeStr += tok->str();
typeStr += "::";
tok = tok->tokAt(2);
}
typeStr += tok->str();
const Token* endTok = tok;
if (Token::Match(endTok, "%name% (|{") && Token::Match(endTok->linkAt(1), ")|} ;") &&
!Token::simpleMatch(endTok->next()->astParent(), ";")) { // for loop condition
return tok;
}
return nullptr;
};
auto isLibraryConstructor = [&](const Token* tok, const std::string& typeStr) -> bool {
const Library::TypeCheck typeCheck = mSettings.library.getTypeCheck("unusedvar", typeStr);
if (typeCheck == Library::TypeCheck::check || typeCheck == Library::TypeCheck::checkFiniteLifetime)
return true;
return mSettings.library.detectContainerOrIterator(tok);
};
const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
std::string typeStr;
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) {
const Token* ctorTok = getConstructorTok(tok, typeStr);
if (ctorTok && (((ctorTok->type() || ctorTok->isStandardType() || (ctorTok->function() && ctorTok->function()->isConstructor())) // TODO: The rhs of || should be removed; It is a workaround for a symboldatabase bug
&& (!ctorTok->function() || ctorTok->function()->isConstructor()) // // is not a function on this scope or is function in this scope and it's a ctor
&& ctorTok->str() != "void") || isLibraryConstructor(tok->next(), typeStr))) {
const Token* parTok = ctorTok->next();
if (Token::simpleMatch(parTok, "=|||>=") && v1 && v1->isKnown()) {
zeroValue = v1;
nonZeroExpr = tok->astOperand2();
} else {
return false;
}
if (const Variable* var = nonZeroExpr->variable())
if (var->typeStartToken()->isTemplateArg())
return suppress;
const ValueType* vt = nonZeroExpr->valueType();
return vt && (vt->pointer || vt->sign == ValueType::UNSIGNED);
}
bool CheckOtherImpl::testIfNonZeroExpressionIsPositive(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr)
{
if (!tok->isComparisonOp() || !tok->astOperand1() || !tok->astOperand2())
return false;
const ValueFlow::Value *v1 = tok->astOperand1()->getValue(0);
const ValueFlow::Value *v2 = tok->astOperand2()->getValue(0);
if (Token::simpleMatch(tok, ">=") && v2 && v2->isKnown()) {
zeroValue = v2;
nonZeroExpr = tok->astOperand1();
} else if (Token::simpleMatch(tok, "isCpp()) {
const ValueType * lhsType = tok->astOperand1()->valueType();
if (!lhsType || !lhsType->isIntegral() || lhsType->pointer)
continue;
}
// bailout if operation is protected by ?:
bool ternary = false;
for (const Token *parent = tok; parent; parent = parent->astParent()) {
if (Token::Match(parent, "?|:")) {
ternary = true;
break;
}
}
if (ternary)
continue;
// Get negative rhs value. preferably a value which doesn't have 'condition'.
if (portability && isNegative(tok->astOperand1(), mSettings))
negativeBitwiseShiftError(tok, 1);
else if (isNegative(tok->astOperand2(), mSettings))
negativeBitwiseShiftError(tok, 2);
}
}
void CheckOtherImpl::negativeBitwiseShiftError(const Token *tok, int op)
{
if (op == 1)
// LHS - this is used by intention in various software, if it
// is used often in a project and works as expected then this is
// a portability issue
reportError(tok, Severity::portability, "shiftNegativeLHS", "Shifting a negative value is technically undefined behaviour", CWE758, Certainty::normal);
else // RHS
reportError(tok, Severity::error, "shiftNegative", "Shifting by a negative value is undefined behaviour", CWE758, Certainty::normal);
}
//---------------------------------------------------------------------------
// Check for incompletely filled buffers.
//---------------------------------------------------------------------------
void CheckOtherImpl::checkIncompleteArrayFill()
{
if (!mSettings.certainty.isEnabled(Certainty::inconclusive))
return;
const bool printWarning = mSettings.severity.isEnabled(Severity::warning);
const bool printPortability = mSettings.severity.isEnabled(Severity::portability);
if (!printPortability && !printWarning)
return;
logChecker("CheckOther::checkIncompleteArrayFill"); // warning,portability,inconclusive
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, "memset|memcpy|memmove (")) {
std::vector args = getArguments(tok);
if (args.size() != 3)
continue;
const Token* tok2 = args[0];
if (tok2->str() == "::")
tok2 = tok2->next();
while (Token::Match(tok2, "%name% ::|."))
tok2 = tok2->tokAt(2);
if (!Token::Match(tok2, "%var% ,"))
continue;
const Variable *var = tok2->variable();
if (!var || !var->isArray() || var->dimensions().empty() || !var->dimension(0))
continue;
if (!args[2]->hasKnownIntValue() || args[2]->getKnownIntValue() != var->dimension(0))
continue;
int size = mTokenizer->sizeOfType(var->typeStartToken());
if (size == 0 && var->valueType()->pointer)
size = mSettings.platform.sizeof_pointer;
else if (size == 0 && var->valueType())
size = var->valueType()->getSizeOf(mSettings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer);
const Token* tok3 = tok->next()->astOperand2()->astOperand1()->astOperand1();
if ((size != 1 && size != 100 && size != 0) || var->isPointer()) {
if (printWarning)
incompleteArrayFillError(tok, tok3->expressionString(), tok->str(), false);
} else if (var->valueType()->type == ValueType::Type::BOOL && printPortability) // sizeof(bool) is not 1 on all platforms
incompleteArrayFillError(tok, tok3->expressionString(), tok->str(), true);
}
}
}
}
void CheckOtherImpl::incompleteArrayFillError(const Token* tok, const std::string& buffer, const std::string& function, bool boolean)
{
if (boolean)
reportError(tok, Severity::portability, "incompleteArrayFill",
"$symbol:" + buffer + "\n"
"$symbol:" + function + "\n"
"Array '" + buffer + "' might be filled incompletely. Did you forget to multiply the size given to '" + function + "()' with 'sizeof(*" + buffer + ")'?\n"
"The array '" + buffer + "' is filled incompletely. The function '" + function + "()' needs the size given in bytes, but the type 'bool' is larger than 1 on some platforms. Did you forget to multiply the size with 'sizeof(*" + buffer + ")'?", CWE131, Certainty::inconclusive);
else
reportError(tok, Severity::warning, "incompleteArrayFill",
"$symbol:" + buffer + "\n"
"$symbol:" + function + "\n"
"Array '" + buffer + "' is filled incompletely. Did you forget to multiply the size given to '" + function + "()' with 'sizeof(*" + buffer + ")'?\n"
"The array '" + buffer + "' is filled incompletely. The function '" + function + "()' needs the size given in bytes, but an element of the given array is larger than one byte. Did you forget to multiply the size with 'sizeof(*" + buffer + ")'?", CWE131, Certainty::inconclusive);
}
//---------------------------------------------------------------------------
// Detect NULL being passed to variadic function.
//---------------------------------------------------------------------------
void CheckOtherImpl::checkVarFuncNullUB()
{
if (!mSettings.severity.isEnabled(Severity::portability))
return;
logChecker("CheckOther::checkVarFuncNullUB"); // portability
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
// Is NULL passed to a function?
if (Token::Match(tok,"[(,] NULL )")) {
// Locate function name in this function call.
const Token *ftok = tok;
int argnr = 1;
while (ftok && ftok->str() != "(") {
if (ftok->str() == ")")
ftok = ftok->link();
else if (ftok->str() == ",")
++argnr;
ftok = ftok->previous();
}
ftok = ftok ? ftok->previous() : nullptr;
if (ftok && ftok->isName()) {
// If this is a variadic function then report error
const Function *f = ftok->function();
if (f && f->argCount() argDef;
tok2 = tok2 ? tok2->link() : nullptr; // goto ')'
if (tok2 && Token::simpleMatch(tok2->tokAt(-1), "..."))
varFuncNullUBError(tok);
}
}
}
}
}
}
void CheckOtherImpl::varFuncNullUBError(const Token *tok)
{
reportError(tok,
Severity::portability,
"varFuncNullUB",
"Passing NULL after the last typed argument to a variadic function leads to undefined behaviour.\n"
"Passing NULL after the last typed argument to a variadic function leads to undefined behaviour.\n"
"The C99 standard, in section 7.15.1.1, states that if the type used by va_arg() is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined.\n"
"The value of the NULL macro is an implementation-defined null pointer constant (7.17), which can be any integer constant expression with the value 0, or such an expression casted to (void*) (6.3.2.3). This includes values like 0, 0L, or even 0LL.\n"
"In practice on common architectures, this will cause real crashes if sizeof(int) != sizeof(void*), and NULL is defined to 0 or any other null pointer constant that promotes to int.\n"
"To reproduce you might be able to use this little code example on 64bit platforms. If the output includes \"ERROR\", the sentinel had only 4 out of 8 bytes initialized to zero and was not detected as the final argument to stop argument processing via va_arg(). Changing the 0 to (void*)0 or 0L will make the \"ERROR\" output go away.\n"
"#include \n"
"#include \n"
"\n"
"void f(char *s, ...) {\n"
" va_list ap;\n"
" va_start(ap,s);\n"
" for (;;) {\n"
" char *p = va_arg(ap,char*);\n"
" printf(\"%018p, %s\\n\", p, (long)p & 255 ? p : \"\");\n"
" if(!p) break;\n"
" }\n"
" va_end(ap);\n"
"}\n"
"\n"
"void g() {\n"
" char *s2 = \"x\";\n"
" char *s3 = \"ERROR\";\n"
"\n"
" // changing 0 to 0L for the 7th argument (which is intended to act as sentinel) makes the error go away on x86_64\n"
" f(\"first\", s2, s2, s2, s2, s2, 0, s3, (char*)0);\n"
"}\n"
"\n"
"void h() {\n"
" int i;\n"
" volatile unsigned char a[1000];\n"
" for (i = 0; istr() == "(" && Token::simpleMatch(tok3->previous(), "sizeof"))
return ChildrenToVisit::none; // don't care about sizeof usage
if (isSameExpression(false, tok->astOperand1(), tok3, settings, true, false))
foundError = true;
return foundError ? ChildrenToVisit::done : ChildrenToVisit::op1_and_op2;
});
return foundError;
}
static bool checkEvaluationOrderCpp11(const Token * tok, const Token * tok2, const Token * parent, const Settings & settings)
{
if (tok->isAssignmentOp()) // TODO check assignment
return false;
if (tok->previous() == tok->astOperand1() && parent->isArithmeticalOp() && parent->isBinaryOp()) {
if (parent->astParent() && parent->astParent()->isAssignmentOp() && isSameExpression(false, tok->astOperand1(), parent->astParent()->astOperand1(), settings, true, false))
return true;
}
bool foundUndefined{false};
visitAstNodes((parent->astOperand1() != tok2) ? parent->astOperand1() : parent->astOperand2(), [&](const Token *tok3) {
if (tok3->str() == "&" && !tok3->astOperand2())
return ChildrenToVisit::none; // don't handle address-of for now
if (tok3->str() == "(" && Token::simpleMatch(tok3->previous(), "sizeof"))
return ChildrenToVisit::none; // don't care about sizeof usage
if (isSameExpression(false, tok->astOperand1(), tok3, settings, true, false))
foundUndefined = true;
return foundUndefined ? ChildrenToVisit::done : ChildrenToVisit::op1_and_op2;
});
return foundUndefined;
}
static bool checkEvaluationOrderCpp17(const Token * tok, const Token * tok2, const Token * parent, const Settings & settings, bool & foundUnspecified)
{
if (tok->isAssignmentOp())
return false;
bool foundUndefined{false};
visitAstNodes((parent->astOperand1() != tok2) ? parent->astOperand1() : parent->astOperand2(), [&](const Token *tok3) {
if (tok3->str() == "&" && !tok3->astOperand2())
return ChildrenToVisit::none; // don't handle address-of for now
if (tok3->str() == "(" && Token::simpleMatch(tok3->previous(), "sizeof"))
return ChildrenToVisit::none; // don't care about sizeof usage
if (isSameExpression(false, tok->astOperand1(), tok3, settings, true, false) && parent->isArithmeticalOp() && parent->isBinaryOp())
foundUndefined = true;
if (tok3->tokType() == Token::eIncDecOp && isSameExpression(false, tok->astOperand1(), tok3->astOperand1(), settings, true, false)) {
if (parent->isArithmeticalOp() && parent->isBinaryOp())
foundUndefined = true;
else
foundUnspecified = true;
}
return (foundUndefined || foundUnspecified) ? ChildrenToVisit::done : ChildrenToVisit::op1_and_op2;
});
return foundUndefined || foundUnspecified;
}
void CheckOtherImpl::checkEvaluationOrder()
{
logChecker("CheckOther::checkEvaluationOrder");
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->isIncDecOp() && !tok->isAssignmentOp())
continue;
if (!tok->astOperand1())
continue;
if (isDesignatedInitializer(tok->astOperand1()))
continue;
for (const Token *tok2 = tok;; tok2 = tok2->astParent()) {
// If ast parent is a sequence point then break
const Token * const parent = tok2->astParent();
if (!parent)
break;
if (Token::Match(parent, "%oror%|&&|?|:|;"))
break;
if (parent->str() == ",") {
const Token *par = parent;
while (Token::simpleMatch(par,","))
par = par->astParent();
// not function or in a while clause => break
if (!(par && par->str() == "(" && par->astOperand2() && par->strAt(-1) != "while"))
break;
// control flow (if|while|etc) => break
if (Token::simpleMatch(par->link(),") {"))
break;
// sequence point in function argument: dostuff((1,2),3) => break
par = par->next();
while (par && (par->previous() != parent))
par = par->nextArgument();
if (!par)
break;
}
if (parent->str() == "(" && parent->astOperand2())
break;
bool foundError{false}, foundUnspecified{false}, bSelfAssignmentError{false};
if (mTokenizer->isCPP() && mSettings.standards.cpp >= Standards::CPP11) {
if (mSettings.standards.cpp >= Standards::CPP17)
foundError = checkEvaluationOrderCpp17(tok, tok2, parent, mSettings, foundUnspecified);
else
foundError = checkEvaluationOrderCpp11(tok, tok2, parent, mSettings);
}
else
foundError = checkEvaluationOrderC(tok, tok2, parent, mSettings, bSelfAssignmentError);
if (foundError) {
unknownEvaluationOrder(parent, foundUnspecified);
break;
}
if (bSelfAssignmentError) {
selfAssignmentError(parent, tok->astOperand1()->expressionString());
break;
}
}
}
}
}
void CheckOtherImpl::unknownEvaluationOrder(const Token* tok, bool isUnspecifiedBehavior)
{
isUnspecifiedBehavior ?
reportError(tok, Severity::portability, "unknownEvaluationOrder",
"Expression '" + (tok ? tok->expressionString() : std::string("x++, x++")) + "' depends on order of evaluation of side effects. Behavior is Unspecified according to c++17", CWE768, Certainty::normal)
: reportError(tok, Severity::error, "unknownEvaluationOrder",
"Expression '" + (tok ? tok->expressionString() : std::string("x = x++;")) + "' depends on order of evaluation of side effects", CWE768, Certainty::normal);
}
void CheckOtherImpl::checkAccessOfMovedVariable()
{
if (!mTokenizer->isCPP() || mSettings.standards.cpp < Standards::CPP11)
return;
if (!mSettings.isPremiumEnabled("accessMoved") && !mSettings.severity.isEnabled(Severity::warning))
return;
logChecker("CheckOther::checkAccessOfMovedVariable"); // c++11,warning
const bool reportInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive);
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
const Token * scopeStart = scope->bodyStart;
if (scope->function) {
const Token * memberInitializationStart = scope->function->constructorMemberInitialization();
if (memberInitializationStart)
scopeStart = memberInitializationStart;
}
for (const Token* tok = scopeStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (!tok->astParent())
continue;
const ValueFlow::Value * movedValue = tok->getMovedValue();
if (!movedValue || movedValue->moveKind == ValueFlow::Value::MoveKind::NonMovedVariable)
continue;
if (movedValue->isInconclusive() && !reportInconclusive)
continue;
bool inconclusive = false;
bool accessOfMoved = false;
if (tok->strAt(1) == ".") {
if (tok->next()->originalName() == "->")
accessOfMoved = true;
else
inconclusive = true;
} else {
const ExprUsage usage = getExprUsage(tok, 0, mSettings);
if (usage == ExprUsage::Used)
accessOfMoved = true;
if (usage == ExprUsage::PassedByReference)
accessOfMoved = !isVariableChangedByFunctionCall(tok, 0, mSettings.library, &inconclusive);
else if (usage == ExprUsage::Inconclusive)
inconclusive = true;
}
if (accessOfMoved || (inconclusive && reportInconclusive))
accessMovedError(tok, tok->str(), movedValue, inconclusive || movedValue->isInconclusive());
}
}
}
void CheckOtherImpl::accessMovedError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive)
{
if (!tok) {
reportError(tok, Severity::warning, "accessMoved", "Access of moved variable 'v'.", CWE672, Certainty::normal);
reportError(tok, Severity::warning, "accessForwarded", "Access of forwarded variable 'v'.", CWE672, Certainty::normal);
return;
}
const char * errorId = nullptr;
std::string kindString;
switch (value->moveKind) {
case ValueFlow::Value::MoveKind::MovedVariable:
errorId = "accessMoved";
kindString = "moved";
break;
case ValueFlow::Value::MoveKind::ForwardedVariable:
errorId = "accessForwarded";
kindString = "forwarded";
break;
default:
return;
}
const std::string errmsg("$symbol:" + varname + "\nAccess of " + kindString + " variable '$symbol'.");
ErrorPath errorPath = getErrorPath(tok, value, errmsg);
reportError(std::move(errorPath), Severity::warning, errorId, errmsg, CWE672, inconclusive ? Certainty::inconclusive : Certainty::normal);
}
void CheckOtherImpl::checkFuncArgNamesDifferent()
{
const bool style = mSettings.severity.isEnabled(Severity::style);
const bool inconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive);
const bool warning = mSettings.severity.isEnabled(Severity::warning);
if (!(warning || (style && inconclusive)) && !mSettings.isPremiumEnabled("funcArgNamesDifferent"))
return;
logChecker("CheckOther::checkFuncArgNamesDifferent"); // style,warning,inconclusive
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
// check every function
for (const Scope *scope : symbolDatabase->functionScopes) {
const Function * function = scope->function;
// only check functions with arguments
if (!function || function->argCount() == 0)
continue;
// only check functions with separate declarations and definitions
if (function->argDef == function->arg)
continue;
// get the function argument name tokens
std::vector declarations(function->argCount());
std::vector definitions(function->argCount());
const Token * decl = function->argDef->next();
for (size_t j = 0; j < function->argCount(); ++j) {
// get the definition
const Variable * variable = function->getArgumentVar(j);
if (variable) {
definitions[j] = variable->nameToken();
}
// get the declaration (search for first token with varId)
while (decl && !Token::Match(decl, "[,;]")) {
// skip everything after the assignment because
// it could also have a varId or be the first
// token with a varId if there is no name token
if (decl->str() == "=") {
decl = decl->nextArgument();
break;
}
// skip over templates and arrays
if (decl->link() && precedes(decl, decl->link()) && !Token::Match(decl, "( [*&]"))
decl = decl->link();
else if (decl->varId())
declarations[j] = decl;
decl = decl->next();
}
if (Token::simpleMatch(decl, ","))
decl = decl->next();
}
// check for different argument order
if (warning) {
bool order_different = false;
for (size_t j = 0; j < function->argCount(); ++j) {
if (!declarations[j] || !definitions[j] || declarations[j]->str() == definitions[j]->str())
continue;
for (size_t k = 0; k < function->argCount(); ++k) {
if (j != k && definitions[k] && declarations[j]->str() == definitions[k]->str()) {
order_different = true;
break;
}
}
}
if (order_different) {
funcArgOrderDifferent(function->name(), function->argDef->next(), function->arg->next(), declarations, definitions);
continue;
}
}
// check for different argument names
if (style && inconclusive) {
for (size_t j = 0; j < function->argCount(); ++j) {
const bool warn = (declarations[j] != nullptr) != (definitions[j] != nullptr) ||
(declarations[j] && definitions[j] && declarations[j]->str() != definitions[j]->str());
if (warn)
funcArgNamesDifferent(function->name(), j, declarations[j], definitions[j]);
}
}
}
}
void CheckOtherImpl::funcArgNamesDifferent(const std::string & functionName, size_t index,
const Token* declaration, const Token* definition)
{
std::list tokens = { declaration,definition };
const std::string id = (declaration != nullptr) == (definition != nullptr) ? "funcArgNamesDifferent" : "funcArgNamesDifferentUnnamed";
reportError(tokens, Severity::style, id,
"$symbol:" + functionName + "\n"
"Function '$symbol' argument " + std::to_string(index + 1) + " names different: declaration '" +
(declaration ? declaration->str() : "") + "' definition '" +
(definition ? definition->str() : "") + "'.", CWE628, Certainty::inconclusive);
}
void CheckOtherImpl::funcArgOrderDifferent(const std::string & functionName,
const Token* declaration, const Token* definition,
const std::vector & declarations,
const std::vector & definitions)
{
std::list tokens = {
!declarations.empty() ? declarations[0] ? declarations[0] : declaration : nullptr,
!definitions.empty() ? definitions[0] ? definitions[0] : definition : nullptr
};
std::string msg = "$symbol:" + functionName + "\nFunction '$symbol' argument order different: declaration '";
for (std::size_t i = 0; i < declarations.size(); ++i) {
if (i != 0)
msg += ", ";
if (declarations[i])
msg += declarations[i]->str();
}
msg += "' definition '";
for (std::size_t i = 0; i < definitions.size(); ++i) {
if (i != 0)
msg += ", ";
if (definitions[i])
msg += definitions[i]->str();
}
msg += "'";
reportError(tokens, Severity::warning, "funcArgOrderDifferent", msg, CWE683, Certainty::normal);
}
static const Token *findShadowed(const Scope *scope, const Variable& var, int linenr)
{
if (!scope)
return nullptr;
for (const Variable &v : scope->varlist) {
if (scope->isExecutable() && v.nameToken()->linenr() > linenr)
continue;
if (v.name() == var.name())
return v.nameToken();
}
auto it = std::find_if(scope->functionList.cbegin(), scope->functionList.cend(), [&](const Function& f) {
return f.type == FunctionType::eFunction && f.name() == var.name()
&& (scope->isClassOrStructOrUnion() || precedes(f.tokenDef, var.nameToken()));
});
if (it != scope->functionList.end())
return it->tokenDef;
if (scope->type == ScopeType::eLambda)
return nullptr;
const Token* shadowed = findShadowed(scope->nestedIn, var, linenr);
if (!shadowed)
shadowed = findShadowed(scope->functionOf, var, linenr);
return shadowed;
}
void CheckOtherImpl::checkShadowVariables()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("shadowVariable"))
return;
logChecker("CheckOther::checkShadowVariables"); // style
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope & scope : symbolDatabase->scopeList) {
if (!scope.isExecutable() || scope.type == ScopeType::eLambda)
continue;
const Scope *functionScope = &scope;
while (functionScope && functionScope->type != ScopeType::eFunction && functionScope->type != ScopeType::eLambda)
functionScope = functionScope->nestedIn;
const auto checkVar = [&](const Variable &var) {
if (!var.nameToken())
return;
if (var.nameToken()->isExpandedMacro()) // #8903
return;
if (!var.isArgument() && functionScope && functionScope->type == ScopeType::eFunction && functionScope->function) {
const auto & argList = functionScope->function->argumentList;
auto it = std::find_if(argList.cbegin(), argList.cend(), [&](const Variable& arg) {
return arg.nameToken() && var.name() == arg.name();
});
if (it != argList.end()) {
shadowError(var.nameToken(), "local variable", it->nameToken(), "argument");
return;
}
}
const Token *shadowed = findShadowed(scope.nestedIn, var, var.nameToken()->linenr());
if (!shadowed)
shadowed = findShadowed(scope.functionOf, var, var.nameToken()->linenr());
if (!shadowed)
return;
if (scope.type == ScopeType::eFunction && scope.className == var.name())
return;
if (functionScope->functionOf && functionScope->functionOf->isClassOrStructOrUnion() && functionScope->function &&
(functionScope->function->isStatic() || functionScope->function->isFriend()) &&
shadowed->variable() && !shadowed->variable()->isLocal())
return;
if (functionScope->functionOf && functionScope->functionOf->isClassOrStructOrUnion() && functionScope->function &&
functionScope->function->isStatic() && shadowed->function() && !shadowed->function()->isStatic())
return;
if (var.scope() && var.scope()->function && var.scope()->function->isConstructor()) {
if (shadowed->variable() && shadowed->variable()->isMember())
return;
if (shadowed->function() && shadowed->function()->nestedIn &&
shadowed->function()->nestedIn->isClassOrStruct())
return;
}
shadowError(var.nameToken(), var.isArgument() ? "argument" : "local variable",
shadowed, (shadowed->varId() != 0) ?
(shadowed->variable()->isMember() ? "member" : "variable") : "function");
};
for (const Variable &var : scope.varlist)
checkVar(var);
if (functionScope && functionScope->type == ScopeType::eFunction && functionScope->function)
for (const Variable &arg: functionScope->function->argumentList)
checkVar(arg);
}
}
void CheckOtherImpl::shadowError(const Token *shadows, const std::string &shadowsType,
const Token *shadowed, const std::string &shadowedType)
{
ErrorPath errorPath;
errorPath.emplace_back(shadowed, "Shadowed " + shadowedType);
errorPath.emplace_back(shadows, "Shadow " + shadowsType);
const std::string &varname = shadows ? shadows->str() : shadowsType;
const std::string ShadowsType = char(std::toupper(shadowsType[0])) + shadowsType.substr(1);
const std::string ShadowedType = char(std::toupper(shadowedType[0])) + shadowedType.substr(1);
const std::string id = "shadow" + ShadowedType;
const std::string message = "$symbol:" + varname + "\n" + ShadowsType + " \'$symbol\' shadows outer " + shadowedType;
reportError(std::move(errorPath), Severity::style, id.c_str(), message, CWE398, Certainty::normal);
}
static bool isVariableExpression(const Token* tok)
{
if (tok->varId() != 0)
return true;
if (Token::simpleMatch(tok, "."))
return isVariableExpression(tok->astOperand1()) &&
isVariableExpression(tok->astOperand2());
if (Token::simpleMatch(tok, "["))
return isVariableExpression(tok->astOperand1());
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 CheckOtherImpl::checkKnownArgument()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("knownArgument"))
return;
logChecker("CheckOther::checkKnownArgument"); // style
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() || tok->isExpandedMacro())
continue;
if (Token::Match(tok, "++|--|%assign%"))
continue;
if (!Token::Match(tok->astParent(), "(|{|,"))
continue;
if (tok->astParent()->isCast() || (tok->isCast() && Token::Match(tok->astOperand2(), "++|--|%assign%")))
continue;
int argn = -1;
const Token* ftok = getTokenArgumentFunction(tok, argn);
if (!ftok)
continue;
if (ftok->isCast())
continue;
if (Token::Match(ftok, "if|while|switch|sizeof"))
continue;
if (tok == tok->astParent()->previous())
continue;
if (isConstVarExpression(tok))
continue;
if (Token::Match(tok->astOperand1(), "%name% ("))
continue;
const Token * tok2 = tok;
if (isCPPCast(tok2))
tok2 = tok2->astOperand2();
if (isVariableExpression(tok2))
continue;
if (tok->isComparisonOp() &&
isSameExpression(
true, tok->astOperand1(), tok->astOperand2(), mSettings, true, true))
continue;
// ensure that there is a integer variable in expression with unknown value
const Token* vartok = nullptr;
visitAstNodes(tok, [&](const Token* child) {
if (Token::Match(child, "%var%|.|[")) {
if (child->hasKnownIntValue())
return ChildrenToVisit::none;
if (astIsIntegral(child, false) && !astIsPointer(child) && child->values().empty()) {
vartok = child;
return ChildrenToVisit::done;
}
}
return ChildrenToVisit::op1_and_op2;
});
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 = ftok->str();
strTolower(funcname);
if (funcname.find("assert") != std::string::npos)
continue;
knownArgumentError(tok, ftok, &tok->values().front(), vartok->expressionString(), isVariableExprHidden(vartok));
}
}
}
void CheckOtherImpl::knownArgumentError(const Token *tok, const Token *ftok, const ValueFlow::Value *value, const std::string &varexpr, bool isVariableExpressionHidden)
{
if (!tok) {
reportError(tok, Severity::style, "knownArgument", "Argument 'x-x' to function 'func' is always 0. It does not matter what value 'x' has.");
reportError(tok, Severity::style, "knownArgumentHiddenVariableExpression", "Argument 'x*0' to function 'func' is always 0. Constant literal calculation disable/hide variable expression 'x'.");
return;
}
const MathLib::bigint intvalue = value->intvalue;
const std::string &expr = tok->expressionString();
const std::string &fun = ftok->str();
std::string ftype = "function ";
if (ftok->type())
ftype = "constructor ";
else if (fun == "{")
ftype = "init list ";
const char *id;
std::string errmsg = "Argument '" + expr + "' to " + ftype + fun + " is always " + MathLib::toString(intvalue) + ". ";
if (!isVariableExpressionHidden) {
id = "knownArgument";
errmsg += "It does not matter what value '" + varexpr + "' has.";
} else {
id = "knownArgumentHiddenVariableExpression";
errmsg += "Constant literal calculation disable/hide variable expression '" + varexpr + "'.";
}
ErrorPath errorPath = getErrorPath(tok, value, errmsg);
reportError(std::move(errorPath), Severity::style, id, errmsg, CWE570, Certainty::normal);
}
void CheckOtherImpl::checkKnownPointerToBool()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("knownPointerToBool"))
return;
logChecker("CheckOther::checkKnownPointerToBool"); // style
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 CheckOtherImpl::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 = bool_to_string(!!value->intvalue);
const std::string& expr = tok->expressionString();
std::string errmsg = "Pointer expression '" + expr + "' converted to bool is always " + cond + ".";
ErrorPath errorPath = getErrorPath(tok, value, errmsg);
reportError(std::move(errorPath), Severity::style, "knownPointerToBool", errmsg, CWE570, Certainty::normal);
}
void CheckOtherImpl::checkComparePointers()
{
logChecker("CheckOther::checkComparePointers");
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope *functionScope : symbolDatabase->functionScopes) {
for (const Token *tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) {
if (!Token::Match(tok, "|=|-"))
continue;
const Token *tok1 = tok->astOperand1();
if (!astIsPointer(tok1))
continue;
const Token *tok2 = tok->astOperand2();
if (!astIsPointer(tok2))
continue;
ValueFlow::Value v1 = ValueFlow::getLifetimeObjValue(tok1);
if (!v1.isLocalLifetimeValue())
continue;
ValueFlow::Value v2 = ValueFlow::getLifetimeObjValue(tok2);
if (!v2.isLocalLifetimeValue())
continue;
const Variable *var1 = v1.tokvalue->variable();
const Variable *var2 = v2.tokvalue->variable();
if (!var1 || !var2)
continue;
if (v1.tokvalue->varId() == v2.tokvalue->varId())
continue;
if (var1->isReference() || var2->isReference())
continue;
if (var1->isRValueReference() || var2->isRValueReference())
continue;
if (const Token* parent2 = getParentLifetime(v2.tokvalue, mSettings.library))
if (var1 == parent2->variable())
continue;
if (const Token* parent1 = getParentLifetime(v1.tokvalue, mSettings.library))
if (var2 == parent1->variable())
continue;
comparePointersError(tok, &v1, &v2);
}
}
}
void CheckOtherImpl::comparePointersError(const Token *tok, const ValueFlow::Value *v1, const ValueFlow::Value *v2)
{
ErrorPath errorPath;
std::string verb = "Comparing";
if (Token::simpleMatch(tok, "-"))
verb = "Subtracting";
const char * const id = (verb[0] == 'C') ? "comparePointers" : "subtractPointers";
if (v1) {
errorPath.emplace_back(v1->tokvalue->variable()->nameToken(), "Variable declared here.");
errorPath.insert(errorPath.end(), v1->errorPath.cbegin(), v1->errorPath.cend());
}
if (v2) {
errorPath.emplace_back(v2->tokvalue->variable()->nameToken(), "Variable declared here.");
errorPath.insert(errorPath.end(), v2->errorPath.cbegin(), v2->errorPath.cend());
}
errorPath.emplace_back(tok, "");
reportError(
std::move(errorPath), Severity::error, id, verb + " pointers that point to different objects", CWE758, Certainty::normal);
}
void CheckOtherImpl::checkModuloOfOne()
{
if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("moduloofone"))
return;
logChecker("CheckOther::checkModuloOfOne"); // style
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (!tok->astOperand2() || !tok->astOperand1())
continue;
if (tok->str() != "%" && tok->str() != "%=")
continue;
if (!tok->valueType() || !tok->valueType()->isIntegral())
continue;
// Value flow..
const ValueFlow::Value *value = tok->astOperand2()->getValue(1LL);
if (value && value->isKnown())
checkModuloOfOneError(tok);
}
}
void CheckOtherImpl::checkModuloOfOneError(const Token *tok)
{
reportError(tok, Severity::style, "moduloofone", "Modulo of one is always equal to zero");
}
static const std::string noname;
struct UnionMember {
UnionMember()
: name(noname)
, size(0) {}
UnionMember(const std::string &name, size_t size)
: name(name)
, size(size) {}
const std::string &name;
size_t size;
};
struct Union {
explicit Union(const Scope &scope)
: scope(&scope)
, name(scope.className) {}
const Scope *scope;
const std::string &name;
std::vector members;
const UnionMember *largestMember() const {
const UnionMember *largest = nullptr;
for (const UnionMember &m : members) {
if (m.size == 0)
return nullptr;
if (largest == nullptr || m.size > largest->size)
largest = &m;
}
return largest;
}
bool isLargestMemberFirst() const {
const UnionMember *largest = largestMember();
return largest == nullptr || largest == &members[0];
}
};
static UnionMember parseUnionMember(const Variable &var,
const Settings &settings)
{
const Token *nameToken = var.nameToken();
if (nameToken == nullptr)
return UnionMember();
const ValueType *vt = nameToken->valueType();
size_t size = 0;
if (var.isArray()) {
size = var.dimension(0);
} else if (vt != nullptr) {
size = vt->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer);
}
return UnionMember(nameToken->str(), size);
}
static std::vector parseUnions(const SymbolDatabase &symbolDatabase,
const Settings &settings)
{
std::vector unions;
for (const Scope &scope : symbolDatabase.scopeList) {
if (scope.type != ScopeType::eUnion)
continue;
Union u(scope);
for (const Variable &var : scope.varlist) {
u.members.push_back(parseUnionMember(var, settings));
}
unions.push_back(std::move(u));
}
return unions;
}
static bool isZeroInitializer(const Token *tok)
{
return Token::Match(tok, "= { 0| } ;");
}
void CheckOtherImpl::checkUnionZeroInit()
{
if (!mSettings.severity.isEnabled(Severity::portability))
return;
logChecker("CheckOther::checkUnionZeroInit"); // portability
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
std::unordered_map unionsByScopeId;
const std::vector unions = parseUnions(*symbolDatabase, mSettings);
for (const Union &u : unions) {
// cppcheck-suppress useStlAlgorithm - std::transform is cumbersome
unionsByScopeId.emplace(u.scope, u);
}
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (!tok->isName() || !isZeroInitializer(tok->next()))
continue;
const ValueType *vt = tok->valueType();
if (vt == nullptr || vt->typeScope == nullptr)
continue;
auto it = unionsByScopeId.find(vt->typeScope);
if (it == unionsByScopeId.end())
continue;
const Union &u = it->second;
if (!u.isLargestMemberFirst()) {
const UnionMember *largestMember = u.largestMember();
if (largestMember == nullptr) {
throw InternalError(tok, "Largest union member not found",
InternalError::INTERNAL);
}
assert(largestMember != nullptr);
unionZeroInitError(tok, *largestMember);
}
}
}
void CheckOtherImpl::unionZeroInitError(const Token *tok,
const UnionMember& largestMember)
{
reportError(tok, Severity::portability, "UnionZeroInit",
(tok != nullptr ? "$symbol:" + tok->str() + "\n" : "") +
"Zero initializing union '$symbol' does not guarantee " +
"its complete storage to be zero initialized as its largest member " +
"is not declared as the first member. Consider making " +
largestMember.name + " the first member or favor memset().");
}
//-----------------------------------------------------------------------------
// Overlapping write (undefined behavior)
//-----------------------------------------------------------------------------
static bool getBufAndOffset(const Token *expr, const Token *&buf, MathLib::bigint *offset, const Settings& settings, MathLib::bigint* sizeValue = nullptr)
{
if (!expr)
return false;
const Token *bufToken, *offsetToken;
MathLib::bigint elementSize = 0;
if (expr->isUnaryOp("&") && Token::simpleMatch(expr->astOperand1(), "[")) {
bufToken = expr->astOperand1()->astOperand1();
offsetToken = expr->astOperand1()->astOperand2();
if (expr->astOperand1()->valueType())
elementSize = expr->astOperand1()->valueType()->getSizeOf(settings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer);
} else if (Token::Match(expr, "+|-") && expr->isBinaryOp()) {
const bool pointer1 = (expr->astOperand1()->valueType() && expr->astOperand1()->valueType()->pointer > 0);
const bool pointer2 = (expr->astOperand2()->valueType() && expr->astOperand2()->valueType()->pointer > 0);
if (pointer1 && !pointer2) {
bufToken = expr->astOperand1();
offsetToken = expr->astOperand2();
auto vt = *expr->astOperand1()->valueType();
--vt.pointer;
elementSize = vt.getSizeOf(settings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer);
} else if (!pointer1 && pointer2) {
bufToken = expr->astOperand2();
offsetToken = expr->astOperand1();
auto vt = *expr->astOperand2()->valueType();
--vt.pointer;
elementSize = vt.getSizeOf(settings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer);
} else {
return false;
}
} else if (expr->valueType() && expr->valueType()->pointer > 0) {
buf = expr;
*offset = 0;
auto vt = *expr->valueType();
--vt.pointer;
elementSize = vt.getSizeOf(settings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer);
if (elementSize > 0) {
*offset *= elementSize;
if (sizeValue)
*sizeValue *= elementSize;
}
return true;
} else {
return false;
}
if (!bufToken->valueType() || !bufToken->valueType()->pointer)
return false;
if (!offsetToken->hasKnownIntValue())
return false;
buf = bufToken;
*offset = offsetToken->getKnownIntValue();
if (elementSize > 0) {
*offset *= elementSize;
if (sizeValue)
*sizeValue *= elementSize;
}
return true;
}
void CheckOtherImpl::checkOverlappingWrite()
{
logChecker("CheckOther::checkOverlappingWrite");
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->isAssignmentOp()) {
// check if LHS is a union member..
const Token * const lhs = tok->astOperand1();
if (!Token::simpleMatch(lhs, ".") || !lhs->isBinaryOp())
continue;
const Variable * const lhsvar = lhs->astOperand1()->variable();
if (!lhsvar || !lhsvar->typeScope() || lhsvar->typeScope()->type != ScopeType::eUnion)
continue;
const Token* const lhsmember = lhs->astOperand2();
if (!lhsmember)
continue;
// Is other union member used in RHS?
const Token *errorToken = nullptr;
visitAstNodes(tok->astOperand2(), [lhsvar, lhsmember, &errorToken](const Token *rhs) {
if (!Token::simpleMatch(rhs, "."))
return ChildrenToVisit::op1_and_op2;
if (!rhs->isBinaryOp() || rhs->astOperand1()->variable() != lhsvar)
return ChildrenToVisit::none;
if (lhsmember->str() == rhs->astOperand2()->str())
return ChildrenToVisit::none;
const Variable* rhsmembervar = rhs->astOperand2()->variable();
const Scope* varscope1 = lhsmember->variable() ? lhsmember->variable()->typeStartToken()->scope() : nullptr;
const Scope* varscope2 = rhsmembervar ? rhsmembervar->typeStartToken()->scope() : nullptr;
if (varscope1 && varscope1 == varscope2 && varscope1 != lhsvar->typeScope())
// lhsmember and rhsmember are declared in same anonymous scope inside union
return ChildrenToVisit::none;
errorToken = rhs->astOperand2();
return ChildrenToVisit::done;
});
if (errorToken)
overlappingWriteUnion(tok);
} else if (Token::Match(tok, "%name% (")) {
const Library::NonOverlappingData *nonOverlappingData = mSettings.library.getNonOverlappingData(tok);
if (!nonOverlappingData)
continue;
const std::vector args = getArguments(tok);
if (nonOverlappingData->ptr1Arg ptr1Arg > args.size())
continue;
if (nonOverlappingData->ptr2Arg ptr2Arg > args.size())
continue;
const Token *ptr1 = args[nonOverlappingData->ptr1Arg - 1];
if (ptr1->hasKnownIntValue() && ptr1->getKnownIntValue() == 0)
continue;
const Token *ptr2 = args[nonOverlappingData->ptr2Arg - 1];
if (ptr2->hasKnownIntValue() && ptr2->getKnownIntValue() == 0)
continue;
// TODO: nonOverlappingData->strlenArg
const int sizeArg = std::max(nonOverlappingData->sizeArg, nonOverlappingData->countArg);
if (sizeArg args.size()) {
if (nonOverlappingData->sizeArg == -1) {
ErrorPath errorPath;
constexpr bool macro = true;
constexpr bool pure = true;
constexpr bool follow = true;
if (!isSameExpression(macro, ptr1, ptr2, mSettings, pure, follow, &errorPath))
continue;
overlappingWriteFunction(tok, tok->str());
}
continue;
}
const bool isCountArg = nonOverlappingData->countArg > 0;
if (!args[sizeArg-1]->hasKnownIntValue())
continue;
MathLib::bigint sizeValue = args[sizeArg-1]->getKnownIntValue();
const Token *buf1, *buf2;
MathLib::bigint offset1, offset2;
if (!getBufAndOffset(ptr1, buf1, &offset1, mSettings, isCountArg ? &sizeValue : nullptr))
continue;
if (!getBufAndOffset(ptr2, buf2, &offset2, mSettings))
continue;
if (offset1 < offset2 && offset1 + sizeValue