2797 lines
132 KiB
C++
2797 lines
132 KiB
C++
/*
|
|
* Cppcheck - A tool for static C/C++ code analysis
|
|
* Copyright (C) 2007-2014 Daniel Marjamäki and 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
#include "checkother.h"
|
|
#include "mathlib.h"
|
|
#include "symboldatabase.h"
|
|
|
|
#include <cmath> // fabs()
|
|
#include <stack>
|
|
#include <algorithm> // find_if()
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Register this check class (by creating a static instance of it)
|
|
namespace {
|
|
CheckOther instance;
|
|
}
|
|
|
|
bool astIsFloat(const Token *tok, bool unknown)
|
|
{
|
|
if (tok->astOperand2() && (tok->str() == "." || tok->str() == "::"))
|
|
return astIsFloat(tok->astOperand2(), unknown);
|
|
|
|
if (tok->astOperand1() && tok->str() != "?" && astIsFloat(tok->astOperand1(),unknown))
|
|
return true;
|
|
if (tok->astOperand2() && astIsFloat(tok->astOperand2(), unknown))
|
|
return true;
|
|
|
|
if (tok->isNumber())
|
|
return MathLib::isFloat(tok->str());
|
|
|
|
if (tok->isName()) {
|
|
// TODO: check function calls, struct members, arrays, etc also
|
|
if (!tok->variable())
|
|
return unknown;
|
|
|
|
return tok->variable()->isFloatingType();
|
|
}
|
|
|
|
if (tok->isOp())
|
|
return false;
|
|
|
|
return unknown;
|
|
}
|
|
|
|
static bool isConstExpression(const Token *tok, const std::set<std::string> &constFunctions)
|
|
{
|
|
if (!tok)
|
|
return true;
|
|
if (tok->isName() && tok->next()->str() == "(") {
|
|
if (!tok->function() && !Token::Match(tok->previous(), ".|::") && constFunctions.find(tok->str()) == constFunctions.end())
|
|
return false;
|
|
else if (tok->function() && !tok->function()->isConst)
|
|
return false;
|
|
}
|
|
if (tok->type() == Token::eIncDecOp)
|
|
return false;
|
|
// bailout when we see ({..})
|
|
if (tok->str() == "{")
|
|
return false;
|
|
return isConstExpression(tok->astOperand1(),constFunctions) && isConstExpression(tok->astOperand2(),constFunctions);
|
|
}
|
|
|
|
bool isSameExpression(const Token *tok1, const Token *tok2, const std::set<std::string> &constFunctions)
|
|
{
|
|
if (tok1 == nullptr && tok2 == nullptr)
|
|
return true;
|
|
if (tok1 == nullptr || tok2 == nullptr)
|
|
return false;
|
|
if (tok1->str() == "." && tok1->astOperand1() && tok1->astOperand1()->str() == "this")
|
|
tok1 = tok1->astOperand2();
|
|
if (tok2->str() == "." && tok2->astOperand1() && tok2->astOperand1()->str() == "this")
|
|
tok2 = tok2->astOperand2();
|
|
if (tok1->str() != tok2->str() || tok1->varId() != tok2->varId())
|
|
return false;
|
|
if (tok1->str() == "." && tok1->originalName() != tok2->originalName())
|
|
return false;
|
|
if (tok1->isExpandedMacro() || tok2->isExpandedMacro())
|
|
return false;
|
|
if (tok1->isName() && tok1->next()->str() == "(") {
|
|
if (!tok1->function() && !Token::Match(tok1->previous(), ".|::") && constFunctions.find(tok1->str()) == constFunctions.end() && !tok1->isAttributeConst() && !tok1->isAttributePure())
|
|
return false;
|
|
else if (tok1->function() && !tok1->function()->isConst && !tok1->function()->isAttributeConst() && !tok1->function()->isAttributePure())
|
|
return false;
|
|
}
|
|
// templates/casts
|
|
if ((Token::Match(tok1, "%var% <") && tok1->next()->link()) ||
|
|
(Token::Match(tok2, "%var% <") && tok2->next()->link())) {
|
|
|
|
// non-const template function that is not a dynamic_cast => return false
|
|
if (Token::simpleMatch(tok1->next()->link(), "> (") &&
|
|
!(tok1->function() && tok1->function()->isConst) &&
|
|
tok1->str() != "dynamic_cast")
|
|
return false;
|
|
|
|
// some template/cast stuff.. check that the template arguments are same
|
|
const Token *t1 = tok1->next();
|
|
const Token *t2 = tok2->next();
|
|
const Token *end1 = tok1->next()->link();
|
|
const Token *end2 = tok2->next()->link();
|
|
while (t1 && t2 && t1 != end1 && t2 != end2) {
|
|
if (t1->str() != t2->str())
|
|
return false;
|
|
t1 = t1->next();
|
|
t2 = t2->next();
|
|
}
|
|
if (t1 != end1 || t2 != end2)
|
|
return false;
|
|
}
|
|
if (tok1->type() == Token::eIncDecOp || tok1->isAssignmentOp())
|
|
return false;
|
|
if (tok1->str() == "(" && tok1->previous() && !tok1->previous()->isName()) { // cast => assert that the casts are equal
|
|
const Token *t1 = tok1->next();
|
|
const Token *t2 = tok2->next();
|
|
while (t1 && t2 && t1->str() == t2->str() && (t1->isName() || t1->str() == "*")) {
|
|
t1 = t1->next();
|
|
t2 = t2->next();
|
|
}
|
|
if (!t1 || !t2 || t1->str() != ")" || t2->str() != ")")
|
|
return false;
|
|
}
|
|
// bailout when we see ({..})
|
|
if (tok1->str() == "{")
|
|
return false;
|
|
bool noncommuative_equals =
|
|
isSameExpression(tok1->astOperand1(), tok2->astOperand1(), constFunctions);
|
|
noncommuative_equals = noncommuative_equals &&
|
|
isSameExpression(tok1->astOperand2(), tok2->astOperand2(), constFunctions);
|
|
|
|
if (noncommuative_equals)
|
|
return true;
|
|
|
|
bool commutative = tok1->astOperand1() && tok1->astOperand2() && Token::Match(tok1, "%or%|%oror%|+|*|&|&&|^|==|!=");
|
|
bool commuative_equals = commutative &&
|
|
isSameExpression(tok1->astOperand2(), tok2->astOperand1(), constFunctions);
|
|
commuative_equals = commuative_equals &&
|
|
isSameExpression(tok1->astOperand1(), tok2->astOperand2(), constFunctions);
|
|
|
|
return commuative_equals;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// 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 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 CheckOther::checkCastIntToCharAndBack()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
std::map<unsigned int, std::string> vars;
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
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 (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 (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";
|
|
}
|
|
}
|
|
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 CheckOther::checkCastIntToCharAndBackError(const Token *tok, const std::string &strFunctionName)
|
|
{
|
|
reportError(
|
|
tok,
|
|
Severity::warning,
|
|
"checkCastIntToCharAndBack",
|
|
"Storing "+ strFunctionName +"() return value in char variable and then comparing with EOF.\n"
|
|
"When saving "+ strFunctionName +"() return value in char variable there is loss of precision. "
|
|
" When "+ strFunctionName +"() returns EOF this value is truncated. Comparing the char "
|
|
"variable with EOF can have unexpected results. For instance a loop \"while (EOF != (c = "+ strFunctionName +"());\" "
|
|
"loops forever on some compilers/platforms and on other compilers/platforms it will stop "
|
|
"when the file contains a matching character."
|
|
);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Clarify calculation precedence for ternary operators.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::clarifyCalculation()
|
|
{
|
|
if (!_settings->isEnabled("style"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
// ? operator where lhs is arithmetical expression
|
|
if (tok->str() != "?" || !tok->astOperand1() || !tok->astOperand1()->isArithmeticalOp() || !tok->astOperand1()->isCalculation())
|
|
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() == ")" || tok2->str() == "?")
|
|
break;
|
|
}
|
|
|
|
if (tok2 && tok2->str() == "?")
|
|
clarifyCalculationError(tok, tok->astOperand1()->str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::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 + "'.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Clarify (meaningless) statements like *foo++; with parentheses.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::clarifyStatement()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart; tok && tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::Match(tok, "* %var%") && tok->astOperand1()) {
|
|
const Token *tok2=tok->previous();
|
|
|
|
while (tok2 && tok2->str() == "*")
|
|
tok2 = tok2->previous();
|
|
|
|
if (Token::Match(tok2, "[{};]")) {
|
|
tok2 = tok->astOperand1();
|
|
if (Token::Match(tok2, "++|-- [;,]"))
|
|
clarifyStatementError(tok2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::clarifyStatementError(const Token *tok)
|
|
{
|
|
reportError(tok, Severity::warning, "clarifyStatement", "Ineffective statement similar to '*A++;'. 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)++;'?");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check for suspicious occurrences of 'if(); {}'.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkSuspiciousSemicolon()
|
|
{
|
|
if (!_settings->inconclusive || !_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
// Look for "if(); {}", "for(); {}" or "while(); {}"
|
|
for (std::list<Scope>::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) {
|
|
if (i->type == Scope::eIf || i->type == Scope::eElse || i->type == Scope::eWhile || i->type == Scope::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(i->classStart, "{ ; } {") &&
|
|
i->classStart->previous()->linenr() == i->classStart->tokAt(2)->linenr()
|
|
&& i->classStart->linenr()+1 >= i->classStart->tokAt(3)->linenr()) {
|
|
SuspiciousSemicolonError(i->classDef);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::SuspiciousSemicolonError(const Token* tok)
|
|
{
|
|
reportError(tok, Severity::warning, "suspiciousSemicolon",
|
|
"Suspicious use of ; at the end of '" + (tok ? tok->str() : std::string()) + "' statement.", true);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// For C++ code, warn if C-style casts are used on pointer types
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::warningOldStylePointerCast()
|
|
{
|
|
// Only valid on C++ code
|
|
if (!_settings->isEnabled("style") || !_tokenizer->isCPP())
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
for (std::size_t i = 0; i < symbolDatabase->functionScopes.size(); ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
const Token* tok;
|
|
if (scope->function && scope->function->isConstructor())
|
|
tok = scope->classDef;
|
|
else
|
|
tok = scope->classStart;
|
|
for (; tok && tok != scope->classEnd; tok = tok->next()) {
|
|
// Old style pointer casting..
|
|
if (!Token::Match(tok, "( const| %type% * const| ) (| %var%|%num%|%bool%|%char%|%str%"))
|
|
continue;
|
|
|
|
// skip first "const" in "const Type* const"
|
|
if (tok->strAt(1) == "const")
|
|
tok = tok->next();
|
|
const Token* typeTok = tok ? tok->next() : nullptr;
|
|
if (!typeTok)
|
|
continue;
|
|
// skip second "const" in "const Type* const"
|
|
if (tok->strAt(3) == "const")
|
|
tok = tok->next();
|
|
|
|
if (tok->strAt(4) == "0") // Casting nullpointers is safe
|
|
continue;
|
|
|
|
// Is "type" a class?
|
|
if (_tokenizer->getSymbolDatabase()->isClassOrStruct(typeTok->str()))
|
|
cstyleCastError(tok);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::cstyleCastError(const Token *tok)
|
|
{
|
|
reportError(tok, Severity::style, "cstyleCast",
|
|
"C-style pointer casting\n"
|
|
"C-style pointer casting detected. C++ offers four different kinds of casts as replacements: "
|
|
"static_cast, const_cast, dynamic_cast and reinterpret_cast. A C-style cast could evaluate to "
|
|
"any of those automatically, thus it is considered safer if the programmer explicitly states "
|
|
"which kind of cast is expected. See also: https://www.securecoding.cert.org/confluence/display/cplusplus/EXP05-CPP.+Do+not+use+C-style+casts.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// float* f; double* d = (double*)f; <-- Pointer cast to a type with an incompatible binary data representation
|
|
//---------------------------------------------------------------------------
|
|
|
|
static std::string analyzeType(const Token* tok)
|
|
{
|
|
if (tok->str() == "double") {
|
|
if (tok->isLong())
|
|
return "long double";
|
|
else
|
|
return "double";
|
|
}
|
|
if (tok->str() == "float")
|
|
return "float";
|
|
if (Token::Match(tok, "int|long|short|char|size_t"))
|
|
return "integer";
|
|
return "";
|
|
}
|
|
|
|
void CheckOther::invalidPointerCast()
|
|
{
|
|
if (!_settings->isEnabled("warning") && !_settings->isEnabled("portability"))
|
|
return;
|
|
|
|
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
const Token* toTok = 0;
|
|
const Token* nextTok = 0;
|
|
// Find cast
|
|
if (Token::Match(tok, "( const| %type% %type%| const| * )")) {
|
|
toTok = tok->next();
|
|
nextTok = tok->link()->next();
|
|
if (nextTok && nextTok->str() == "(")
|
|
nextTok = nextTok->next();
|
|
} else if (Token::Match(tok, "reinterpret_cast < const| %type% %type%| const| * > (")) {
|
|
nextTok = tok->tokAt(5);
|
|
while (nextTok->str() != "(")
|
|
nextTok = nextTok->next();
|
|
nextTok = nextTok->next();
|
|
toTok = tok->tokAt(2);
|
|
}
|
|
if (toTok && toTok->str() == "const")
|
|
toTok = toTok->next();
|
|
|
|
if (!nextTok || !toTok || !toTok->isStandardType())
|
|
continue;
|
|
|
|
// Find casted variable
|
|
const Variable *var = nullptr;
|
|
bool allocation = false;
|
|
bool ref = false;
|
|
if (Token::Match(nextTok, "new %type%"))
|
|
allocation = true;
|
|
else if (Token::Match(nextTok, "%var% !!["))
|
|
var = nextTok->variable();
|
|
else if (Token::Match(nextTok, "& %var%") && !Token::Match(nextTok->tokAt(2), "(|[")) {
|
|
var = nextTok->next()->variable();
|
|
ref = true;
|
|
}
|
|
|
|
const Token* fromTok = 0;
|
|
|
|
if (allocation) {
|
|
fromTok = nextTok->next();
|
|
} else {
|
|
if (!var || (!ref && !var->isPointer() && !var->isArray()) || (ref && (var->isPointer() || var->isArray())))
|
|
continue;
|
|
fromTok = var->typeStartToken();
|
|
}
|
|
|
|
while (Token::Match(fromTok, "static|const"))
|
|
fromTok = fromTok->next();
|
|
if (!fromTok->isStandardType())
|
|
continue;
|
|
|
|
std::string fromType = analyzeType(fromTok);
|
|
std::string toType = analyzeType(toTok);
|
|
if (fromType != toType && !fromType.empty() && !toType.empty() && _settings->isEnabled("portability") && (toTok->str() != "char" || _settings->inconclusive))
|
|
invalidPointerCastError(tok, fromType, toType, toTok->str() == "char");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive)
|
|
{
|
|
if (to == "integer") { // If we cast something to int*, this can be useful to play with its binary data representation
|
|
if (!inconclusive)
|
|
reportError(tok, Severity::portability, "invalidPointerCast", "Casting from " + from + "* to integer* is not portable due to different binary data representations on different platforms.");
|
|
else
|
|
reportError(tok, Severity::portability, "invalidPointerCast", "Casting from " + from + "* to char* is not portable due to different binary data representations on different platforms.", true);
|
|
} else
|
|
reportError(tok, Severity::portability, "invalidPointerCast", "Casting between " + from + "* and " + to + "* which have an incompatible binary data representation.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// This check detects errors on POSIX systems, when a pipe command called
|
|
// with a wrong dimensioned file descriptor array. The pipe command requires
|
|
// exactly an integer array of dimension two as parameter.
|
|
//
|
|
// References:
|
|
// - http://linux.die.net/man/2/pipe
|
|
// - ticket #3521
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkPipeParameterSize()
|
|
{
|
|
if (!_settings->standards.posix)
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::Match(tok, "pipe ( %var% )") ||
|
|
Token::Match(tok, "pipe2 ( %var% ,")) {
|
|
const Token * const varTok = tok->tokAt(2);
|
|
|
|
const Variable *var = varTok->variable();
|
|
MathLib::bigint dim;
|
|
if (var && var->isArray() && !var->isArgument() && ((dim=var->dimension(0U)) < 2)) {
|
|
const std::string strDim = MathLib::toString(dim);
|
|
checkPipeParameterSizeError(varTok,varTok->str(), strDim);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::checkPipeParameterSizeError(const Token *tok, const std::string &strVarName, const std::string &strDim)
|
|
{
|
|
reportError(tok, Severity::error,
|
|
"wrongPipeParameterSize", "Buffer '" + strVarName + "' must have size of 2 integers if used as parameter of pipe().\n"
|
|
"The pipe()/pipe2() system command takes an argument, which is an array of exactly two integers.\n"
|
|
"The variable '" + strVarName + "' is an array of size " + strDim + ", which does not match.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Detect redundant assignments: x = 0; x = 4;
|
|
//---------------------------------------------------------------------------
|
|
|
|
static bool nonLocal(const Variable* var)
|
|
{
|
|
return !var || (!var->isLocal() && !var->isArgument()) || var->isStatic() || var->isReference();
|
|
}
|
|
|
|
static void eraseNotLocalArg(std::map<unsigned int, const Token*>& container, const SymbolDatabase* symbolDatabase)
|
|
{
|
|
for (std::map<unsigned int, const Token*>::iterator i = container.begin(); i != container.end();) {
|
|
const Variable* var = symbolDatabase->getVariableFromVarId(i->first);
|
|
if (!var || nonLocal(var)) {
|
|
container.erase(i++);
|
|
if (i == container.end())
|
|
break;
|
|
} else
|
|
++i;
|
|
}
|
|
}
|
|
|
|
static void eraseMemberAssignments(const unsigned int varId, std::map<unsigned int, std::set<unsigned int> > &membervars, std::map<unsigned int, const Token*> &varAssignments)
|
|
{
|
|
const std::map<unsigned int, std::set<unsigned int> >::const_iterator it = membervars.find(varId);
|
|
if (it != membervars.end()) {
|
|
const std::set<unsigned int> v = it->second;
|
|
for (std::set<unsigned int>::const_iterator vit = v.begin(); vit != v.end(); ++vit) {
|
|
varAssignments.erase(*vit);
|
|
if (*vit != varId)
|
|
eraseMemberAssignments(*vit, membervars, varAssignments);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::checkRedundantAssignment()
|
|
{
|
|
const bool performance = _settings->isEnabled("performance");
|
|
const bool warning = _settings->isEnabled("warning");
|
|
if (!warning && !performance)
|
|
return;
|
|
|
|
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
for (std::list<Scope>::const_iterator scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) {
|
|
if (!scope->isExecutable())
|
|
continue;
|
|
|
|
std::map<unsigned int, const Token*> varAssignments;
|
|
std::map<unsigned int, const Token*> memAssignments;
|
|
std::map<unsigned int, std::set<unsigned int> > membervars;
|
|
std::set<unsigned int> initialized;
|
|
const Token* writtenArgumentsEnd = 0;
|
|
|
|
for (const Token* tok = scope->classStart->next(); tok && tok != scope->classEnd; tok = tok->next()) {
|
|
if (tok == writtenArgumentsEnd)
|
|
writtenArgumentsEnd = 0;
|
|
|
|
if (tok->str() == "?" && tok->astOperand2()) {
|
|
tok = Token::findmatch(tok->astOperand2(), ";|}");
|
|
if (!tok)
|
|
break;
|
|
varAssignments.clear();
|
|
memAssignments.clear();
|
|
} else if (tok->str() == "{" && tok->strAt(-1) != "{" && tok->strAt(-1) != "=" && tok->strAt(-4) != "case" && tok->strAt(-3) != "default") { // conditional or non-executable inner scope: Skip it and reset status
|
|
tok = tok->link();
|
|
varAssignments.clear();
|
|
memAssignments.clear();
|
|
} else if (Token::Match(tok, "for|if|while (")) {
|
|
tok = tok->linkAt(1);
|
|
} else if (Token::Match(tok, "break|return|continue|throw|goto")) {
|
|
varAssignments.clear();
|
|
memAssignments.clear();
|
|
} else if (tok->type() == Token::eVariable && !Token::Match(tok,"%var% (")) {
|
|
// Set initialization flag
|
|
if (!Token::Match(tok, "%var% ["))
|
|
initialized.insert(tok->varId());
|
|
else {
|
|
const Token *tok2 = tok->next();
|
|
while (tok2 && tok2->str() == "[")
|
|
tok2 = tok2->link()->next();
|
|
if (tok2 && tok2->str() != ";")
|
|
initialized.insert(tok->varId());
|
|
}
|
|
|
|
const Token *startToken = tok;
|
|
while (Token::Match(startToken, "%var%|::|.")) {
|
|
startToken = startToken->previous();
|
|
if (Token::Match(startToken, "%var% . %var%"))
|
|
membervars[startToken->varId()].insert(startToken->tokAt(2)->varId());
|
|
}
|
|
|
|
std::map<unsigned int, const Token*>::iterator it = varAssignments.find(tok->varId());
|
|
if (tok->next()->isAssignmentOp() && Token::Match(startToken, "[;{}]")) { // Assignment
|
|
if (it != varAssignments.end()) {
|
|
bool error = true; // Ensure that variable is not used on right side
|
|
for (const Token* tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) {
|
|
if (tok2->str() == ";")
|
|
break;
|
|
else if (tok2->varId() == tok->varId())
|
|
error = false;
|
|
else if (Token::Match(tok2, "%var% (") && nonLocal(tok->variable())) { // Called function might use the variable
|
|
const Function* const func = tok2->function();
|
|
const Variable* const var = tok->variable();
|
|
if (!var || var->isGlobal() || var->isReference() || ((!func || func->nestedIn) && tok2->strAt(-1) != ".")) // Global variable, or member function
|
|
error = false;
|
|
}
|
|
}
|
|
if (error) {
|
|
if (scope->type == Scope::eSwitch && Token::findmatch(it->second, "default|case", tok) && warning)
|
|
redundantAssignmentInSwitchError(it->second, tok, tok->str());
|
|
else if (performance) {
|
|
const bool nonlocal = nonLocal(it->second->variable());
|
|
if (_settings->inconclusive || !nonlocal) // see #5089 - report inconclusive only when requested
|
|
redundantAssignmentError(it->second, tok, tok->str(), nonlocal); // Inconclusive for non-local variables
|
|
}
|
|
}
|
|
it->second = tok;
|
|
}
|
|
if (!Token::simpleMatch(tok->tokAt(2), "0 ;") || (tok->variable() && tok->variable()->nameToken() != tok->tokAt(-2)))
|
|
varAssignments[tok->varId()] = tok;
|
|
memAssignments.erase(tok->varId());
|
|
eraseMemberAssignments(tok->varId(), membervars, varAssignments);
|
|
} else if (tok->next()->type() == Token::eIncDecOp || (tok->previous()->type() == Token::eIncDecOp && tok->strAt(1) == ";")) { // Variable incremented/decremented; Prefix-Increment is only suspicious, if its return value is unused
|
|
varAssignments[tok->varId()] = tok;
|
|
memAssignments.erase(tok->varId());
|
|
eraseMemberAssignments(tok->varId(), membervars, varAssignments);
|
|
} else if (!Token::simpleMatch(tok->tokAt(-2), "sizeof (")) { // Other usage of variable
|
|
if (it != varAssignments.end())
|
|
varAssignments.erase(it);
|
|
if (!writtenArgumentsEnd) // Indicates that we are in the first argument of strcpy/memcpy/... function
|
|
memAssignments.erase(tok->varId());
|
|
}
|
|
} else if (Token::Match(tok, "%var% (") && _settings->library.functionpure.find(tok->str()) == _settings->library.functionpure.end()) { // Function call. Global variables might be used. Reset their status
|
|
const bool memfunc = Token::Match(tok, "memcpy|memmove|memset|strcpy|strncpy|sprintf|snprintf|strcat|strncat|wcscpy|wcsncpy|swprintf|wcscat|wcsncat");
|
|
if (tok->varId()) // operator() or function pointer
|
|
varAssignments.erase(tok->varId());
|
|
|
|
if (memfunc && tok->strAt(-1) != "(" && tok->strAt(-1) != "=") {
|
|
const Token* param1 = tok->tokAt(2);
|
|
writtenArgumentsEnd = param1->next();
|
|
if (param1->varId() && param1->strAt(1) == "," && !Token::Match(tok, "strcat|strncat|wcscat|wcsncat")) {
|
|
if (tok->str() == "memset" && initialized.find(param1->varId()) == initialized.end() && param1->variable() && param1->variable()->isLocal() && param1->variable()->isArray())
|
|
initialized.insert(param1->varId());
|
|
else if (memAssignments.find(param1->varId()) == memAssignments.end())
|
|
memAssignments[param1->varId()] = tok;
|
|
else {
|
|
const std::map<unsigned int, const Token*>::iterator it = memAssignments.find(param1->varId());
|
|
if (warning && scope->type == Scope::eSwitch && Token::findmatch(it->second, "default|case", tok))
|
|
redundantCopyInSwitchError(it->second, tok, param1->str());
|
|
else if (performance)
|
|
redundantCopyError(it->second, tok, param1->str());
|
|
}
|
|
}
|
|
} else if (scope->type == Scope::eSwitch) { // Avoid false positives if noreturn function is called in switch
|
|
const Function* const func = tok->function();
|
|
if (!func || !func->hasBody) {
|
|
varAssignments.clear();
|
|
memAssignments.clear();
|
|
continue;
|
|
}
|
|
const Token* funcEnd = func->functionScope->classEnd;
|
|
bool noreturn;
|
|
if (!_tokenizer->IsScopeNoReturn(funcEnd, &noreturn) && !noreturn) {
|
|
eraseNotLocalArg(varAssignments, symbolDatabase);
|
|
eraseNotLocalArg(memAssignments, symbolDatabase);
|
|
} else {
|
|
varAssignments.clear();
|
|
memAssignments.clear();
|
|
}
|
|
} else { // Noreturn functions outside switch don't cause problems
|
|
eraseNotLocalArg(varAssignments, symbolDatabase);
|
|
eraseNotLocalArg(memAssignments, symbolDatabase);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::redundantCopyError(const Token *tok1, const Token* tok2, const std::string& var)
|
|
{
|
|
std::list<const Token*> callstack;
|
|
callstack.push_back(tok1);
|
|
callstack.push_back(tok2);
|
|
reportError(callstack, Severity::performance, "redundantCopy",
|
|
"Buffer '" + var + "' is being written before its old content has been used.");
|
|
}
|
|
|
|
void CheckOther::redundantCopyInSwitchError(const Token *tok1, const Token* tok2, const std::string &var)
|
|
{
|
|
std::list<const Token*> callstack;
|
|
callstack.push_back(tok1);
|
|
callstack.push_back(tok2);
|
|
reportError(callstack, Severity::warning, "redundantCopyInSwitch",
|
|
"Buffer '" + var + "' is being written before its old content has been used. 'break;' missing?");
|
|
}
|
|
|
|
void CheckOther::redundantAssignmentError(const Token *tok1, const Token* tok2, const std::string& var, bool inconclusive)
|
|
{
|
|
std::list<const Token*> callstack;
|
|
callstack.push_back(tok1);
|
|
callstack.push_back(tok2);
|
|
if (inconclusive)
|
|
reportError(callstack, Severity::performance, "redundantAssignment",
|
|
"Variable '" + var + "' is reassigned a value before the old one has been used if variable is no semaphore variable.\n"
|
|
"Variable '" + var + "' is reassigned a value before the old one has been used. Make sure that this variable is not used like a semaphore in a threading environment before simplifying this code.", true);
|
|
else
|
|
reportError(callstack, Severity::performance, "redundantAssignment",
|
|
"Variable '" + var + "' is reassigned a value before the old one has been used.");
|
|
}
|
|
|
|
void CheckOther::redundantAssignmentInSwitchError(const Token *tok1, const Token* tok2, const std::string &var)
|
|
{
|
|
std::list<const Token*> callstack;
|
|
callstack.push_back(tok1);
|
|
callstack.push_back(tok2);
|
|
reportError(callstack, Severity::warning, "redundantAssignInSwitch",
|
|
"Variable '" + var + "' is reassigned a value before the old one has been used. 'break;' missing?");
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// switch (x)
|
|
// {
|
|
// case 2:
|
|
// y = a; // <- this assignment is redundant
|
|
// case 3:
|
|
// y = b; // <- case 2 falls through and sets y twice
|
|
// }
|
|
//---------------------------------------------------------------------------
|
|
static inline bool isFunctionOrBreakPattern(const Token *tok)
|
|
{
|
|
if (Token::Match(tok, "%var% (") || Token::Match(tok, "break|continue|return|exit|goto|throw"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CheckOther::checkRedundantAssignmentInSwitch()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
// Find the beginning of a switch. E.g.:
|
|
// switch (var) { ...
|
|
for (std::list<Scope>::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) {
|
|
if (i->type != Scope::eSwitch || !i->classStart)
|
|
continue;
|
|
|
|
// Check the contents of the switch statement
|
|
std::map<unsigned int, const Token*> varsWithBitsSet;
|
|
std::map<unsigned int, std::string> bitOperations;
|
|
|
|
for (const Token *tok2 = i->classStart->next(); tok2 != i->classEnd; tok2 = tok2->next()) {
|
|
if (tok2->str() == "{") {
|
|
// Inside a conditional or loop. Don't mark variable accesses as being redundant. E.g.:
|
|
// case 3: b = 1;
|
|
// case 4: if (a) { b = 2; } // Doesn't make the b=1 redundant because it's conditional
|
|
if (Token::Match(tok2->previous(), ")|else {") && tok2->link()) {
|
|
const Token* endOfConditional = tok2->link();
|
|
for (const Token* tok3 = tok2; tok3 != endOfConditional; tok3 = tok3->next()) {
|
|
if (tok3->varId() != 0) {
|
|
varsWithBitsSet.erase(tok3->varId());
|
|
bitOperations.erase(tok3->varId());
|
|
} else if (isFunctionOrBreakPattern(tok3)) {
|
|
varsWithBitsSet.clear();
|
|
bitOperations.clear();
|
|
}
|
|
}
|
|
tok2 = endOfConditional;
|
|
}
|
|
}
|
|
|
|
// Variable assignment. Report an error if it's assigned to twice before a break. E.g.:
|
|
// case 3: b = 1; // <== redundant
|
|
// case 4: b = 2;
|
|
|
|
if (Token::Match(tok2->previous(), ";|{|}|: %var% = %any% ;") && tok2->varId() != 0) {
|
|
varsWithBitsSet.erase(tok2->varId());
|
|
bitOperations.erase(tok2->varId());
|
|
}
|
|
|
|
// Bitwise operation. Report an error if it's performed twice before a break. E.g.:
|
|
// case 3: b |= 1; // <== redundant
|
|
// case 4: b |= 1;
|
|
else if (Token::Match(tok2->previous(), ";|{|}|: %var% = %var% %or%|& %num% ;") &&
|
|
tok2->varId() != 0 && tok2->varId() == tok2->tokAt(2)->varId()) {
|
|
std::string bitOp = tok2->strAt(3) + tok2->strAt(4);
|
|
std::map<unsigned int, const Token*>::iterator i2 = varsWithBitsSet.find(tok2->varId());
|
|
|
|
// This variable has not had a bit operation performed on it yet, so just make a note of it
|
|
if (i2 == varsWithBitsSet.end()) {
|
|
varsWithBitsSet[tok2->varId()] = tok2;
|
|
bitOperations[tok2->varId()] = bitOp;
|
|
}
|
|
|
|
// The same bit operation has been performed on the same variable twice, so report an error
|
|
else if (bitOperations[tok2->varId()] == bitOp)
|
|
redundantBitwiseOperationInSwitchError(i2->second, i2->second->str());
|
|
|
|
// A different bit operation was performed on the variable, so clear it
|
|
else {
|
|
varsWithBitsSet.erase(tok2->varId());
|
|
bitOperations.erase(tok2->varId());
|
|
}
|
|
}
|
|
|
|
// Not a simple assignment so there may be good reason if this variable is assigned to twice. E.g.:
|
|
// case 3: b = 1;
|
|
// case 4: b++;
|
|
else if (tok2->varId() != 0 && tok2->strAt(1) != "|" && tok2->strAt(1) != "&") {
|
|
varsWithBitsSet.erase(tok2->varId());
|
|
bitOperations.erase(tok2->varId());
|
|
}
|
|
|
|
// Reset our record of assignments if there is a break or function call. E.g.:
|
|
// case 3: b = 1; break;
|
|
if (isFunctionOrBreakPattern(tok2)) {
|
|
varsWithBitsSet.clear();
|
|
bitOperations.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::redundantBitwiseOperationInSwitchError(const Token *tok, const std::string &varname)
|
|
{
|
|
reportError(tok, Severity::warning,
|
|
"redundantBitwiseOperationInSwitch", "Redundant bitwise operation on '" + varname + "' in 'switch' statement. 'break;' missing?");
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Detect fall through cases (experimental).
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkSwitchCaseFallThrough()
|
|
{
|
|
if (!(_settings->isEnabled("style") && _settings->experimental))
|
|
return;
|
|
|
|
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
for (std::list<Scope>::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) {
|
|
if (i->type != Scope::eSwitch || !i->classStart) // Find the beginning of a switch
|
|
continue;
|
|
|
|
// Check the contents of the switch statement
|
|
std::stack<std::pair<Token *, bool> > ifnest;
|
|
bool justbreak = true;
|
|
bool firstcase = true;
|
|
for (const Token *tok2 = i->classStart; tok2 != i->classEnd; tok2 = tok2->next()) {
|
|
if (Token::simpleMatch(tok2, "if (")) {
|
|
tok2 = tok2->next()->link()->next();
|
|
ifnest.push(std::make_pair(tok2->link(), false));
|
|
justbreak = false;
|
|
} else if (Token::simpleMatch(tok2, "while (")) {
|
|
tok2 = tok2->next()->link()->next();
|
|
if (tok2->link()) // skip over "do { } while ( ) ;" case
|
|
tok2 = tok2->link();
|
|
justbreak = false;
|
|
} else if (Token::simpleMatch(tok2, "do {")) {
|
|
tok2 = tok2->next()->link();
|
|
justbreak = false;
|
|
} else if (Token::simpleMatch(tok2, "for (")) {
|
|
tok2 = tok2->next()->link()->next()->link();
|
|
justbreak = false;
|
|
} else if (Token::simpleMatch(tok2, "switch (")) {
|
|
// skip over nested switch, we'll come to that soon
|
|
tok2 = tok2->next()->link()->next()->link();
|
|
} else if (Token::Match(tok2, "break|continue|return|exit|goto|throw")) {
|
|
justbreak = true;
|
|
tok2 = Token::findsimplematch(tok2, ";");
|
|
} else if (Token::Match(tok2, "case|default")) {
|
|
if (!justbreak && !firstcase) {
|
|
switchCaseFallThrough(tok2);
|
|
}
|
|
tok2 = Token::findsimplematch(tok2, ":");
|
|
justbreak = true;
|
|
firstcase = false;
|
|
} else if (tok2->str() == "}") {
|
|
if (!ifnest.empty() && tok2 == ifnest.top().first) {
|
|
if (tok2->next()->str() == "else") {
|
|
tok2 = tok2->tokAt(2);
|
|
ifnest.pop();
|
|
ifnest.push(std::make_pair(tok2->link(), justbreak));
|
|
justbreak = false;
|
|
} else {
|
|
justbreak &= ifnest.top().second;
|
|
ifnest.pop();
|
|
}
|
|
}
|
|
} else if (tok2->str() != ";") {
|
|
justbreak = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::switchCaseFallThrough(const Token *tok)
|
|
{
|
|
reportError(tok, Severity::style,
|
|
"switchCaseFallThrough", "Switch falls through case without comment. 'break;' missing?");
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check for statements like case A||B: in switch()
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkSuspiciousCaseInSwitch()
|
|
{
|
|
if (!_settings->inconclusive || !_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
for (std::list<Scope>::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) {
|
|
if (i->type != Scope::eSwitch)
|
|
continue;
|
|
|
|
for (const Token* tok = i->classStart->next(); tok != i->classEnd; tok = tok->next()) {
|
|
if (tok->str() == "case") {
|
|
const Token* finding = nullptr;
|
|
for (const Token* tok2 = tok->next(); tok2; tok2 = tok2->next()) {
|
|
if (tok2->str() == ":")
|
|
break;
|
|
if (Token::Match(tok2, "[;}{]"))
|
|
break;
|
|
if (tok2->str() == "?")
|
|
finding = nullptr;
|
|
|
|
if (Token::Match(tok2, "&&|%oror%"))
|
|
finding = tok2;
|
|
}
|
|
if (finding)
|
|
suspiciousCaseInSwitchError(finding, finding->str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::suspiciousCaseInSwitchError(const Token* tok, const std::string& operatorString)
|
|
{
|
|
reportError(tok, Severity::warning, "suspiciousCase",
|
|
"Found suspicious case label in switch(). Operator '" + operatorString + "' probably doesn't work as intended.\n"
|
|
"Using an operator like '" + operatorString + "' in a case label is suspicious. Did you intend to use a bitwise operator, multiple case labels or if/else instead?", true);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// if (x == 1)
|
|
// x == 0; // <- suspicious equality comparison.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkSuspiciousEqualityComparison()
|
|
{
|
|
if (!_settings->isEnabled("warning") || !_settings->inconclusive)
|
|
return;
|
|
|
|
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::simpleMatch(tok, "for (")) {
|
|
const Token* const openParen = tok->next();
|
|
const Token* const closeParen = tok->linkAt(1);
|
|
|
|
// Search for any suspicious equality comparison in the initialization
|
|
// or increment-decrement parts of the for() loop.
|
|
// For example:
|
|
// for (i == 2; i < 10; i++)
|
|
// or
|
|
// for (i = 0; i < 10; i == a)
|
|
if (Token::Match(openParen->next(), "%var% =="))
|
|
suspiciousEqualityComparisonError(openParen->tokAt(2));
|
|
if (Token::Match(closeParen->tokAt(-2), "== %any%"))
|
|
suspiciousEqualityComparisonError(closeParen->tokAt(-2));
|
|
|
|
// Equality comparisons with 0 are simplified to negation. For instance,
|
|
// (x == 0) is simplified to (!x), so also check for suspicious negation
|
|
// in the initialization or increment-decrement parts of the for() loop.
|
|
// For example:
|
|
// for (!i; i < 10; i++)
|
|
if (Token::Match(openParen->next(), "! %var%"))
|
|
suspiciousEqualityComparisonError(openParen->next());
|
|
if (Token::Match(closeParen->tokAt(-2), "! %var%"))
|
|
suspiciousEqualityComparisonError(closeParen->tokAt(-2));
|
|
|
|
// Skip over for() loop conditions because "for (;running==1;)"
|
|
// is a bit strange, but not necessarily incorrect.
|
|
tok = closeParen;
|
|
} else if (Token::Match(tok, "[;{}] *| %var% == %any% ;")) {
|
|
|
|
// Exclude compound statements surrounded by parentheses, such as
|
|
// printf("%i\n", ({x==0;}));
|
|
// because they may appear as an expression in GNU C/C++.
|
|
// See http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
|
|
const Token* afterStatement = tok->strAt(1) == "*" ? tok->tokAt(6) : tok->tokAt(5);
|
|
if (!Token::simpleMatch(afterStatement, "} )"))
|
|
suspiciousEqualityComparisonError(tok->next());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::suspiciousEqualityComparisonError(const Token* tok)
|
|
{
|
|
reportError(tok, Severity::warning, "suspiciousEqualityComparison",
|
|
"Found suspicious equality comparison. Did you intend to assign a value instead?", true);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// strtol(str, 0, radix) <- radix must be 0 or 2-36
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::invalidFunctionUsage()
|
|
{
|
|
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (!Token::Match(tok, "%var% ( !!)"))
|
|
continue;
|
|
const std::string& functionName = tok->str();
|
|
int argnr = 1;
|
|
const Token *argtok = tok->tokAt(2);
|
|
while (argtok && argtok->str() != ")") {
|
|
if (Token::Match(argtok,"%num% [,)]")) {
|
|
if (MathLib::isInt(argtok->str()) &&
|
|
!_settings->library.isargvalid(functionName, argnr, MathLib::toLongNumber(argtok->str())))
|
|
invalidFunctionArgError(argtok,functionName,argnr,_settings->library.validarg(functionName,argnr));
|
|
} else {
|
|
const Token *top = argtok;
|
|
while (top->astParent() && top->astParent()->str() != "," && top->astParent() != tok->next())
|
|
top = top->astParent();
|
|
if (top->isComparisonOp() || Token::Match(top, "%oror%|&&")) {
|
|
if (_settings->library.isboolargbad(functionName, argnr))
|
|
invalidFunctionArgBoolError(top, functionName, argnr);
|
|
|
|
// Are the values 0 and 1 valid?
|
|
else if (!_settings->library.isargvalid(functionName, argnr, 0))
|
|
invalidFunctionArgError(top, functionName, argnr, _settings->library.validarg(functionName,argnr));
|
|
else if (!_settings->library.isargvalid(functionName, argnr, 1))
|
|
invalidFunctionArgError(top, functionName, argnr, _settings->library.validarg(functionName,argnr));
|
|
}
|
|
}
|
|
argnr++;
|
|
argtok = argtok->nextArgument();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::invalidFunctionArgError(const Token *tok, const std::string &functionName, int argnr, const std::string &validstr)
|
|
{
|
|
std::ostringstream errmsg;
|
|
errmsg << "Invalid " << functionName << "() argument nr " << argnr;
|
|
if (!tok)
|
|
;
|
|
else if (tok->isNumber())
|
|
errmsg << ". The value is " << tok->str() << " but the valid values are '" << validstr << "'.";
|
|
else if (tok->isComparisonOp())
|
|
errmsg << ". The value is 0 or 1 (comparison result) but the valid values are '" << validstr << "'.";
|
|
reportError(tok, Severity::error, "invalidFunctionArg", errmsg.str());
|
|
}
|
|
|
|
void CheckOther::invalidFunctionArgBoolError(const Token *tok, const std::string &functionName, int argnr)
|
|
{
|
|
std::ostringstream errmsg;
|
|
errmsg << "Invalid " << functionName << "() argument nr " << argnr << ". A non-boolean value is required.";
|
|
reportError(tok, Severity::error, "invalidFunctionArgBool", errmsg.str());
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Find consecutive return, break, continue, goto or throw statements. e.g.:
|
|
// break; break;
|
|
// Detect dead code, that follows such a statement. e.g.:
|
|
// return(0); foo();
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkUnreachableCode()
|
|
{
|
|
if (!_settings->isEnabled("style"))
|
|
return;
|
|
|
|
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
|
|
for (const Token* tok = scope->classStart; tok && tok != scope->classEnd; tok = tok->next()) {
|
|
const Token* secondBreak = 0;
|
|
const Token* labelName = 0;
|
|
if (tok->link() && Token::Match(tok, "(|[|<"))
|
|
tok = tok->link();
|
|
else if (Token::Match(tok, "break|continue ;"))
|
|
secondBreak = tok->tokAt(2);
|
|
else if (Token::Match(tok, "[;{}:] return|throw")) {
|
|
tok = tok->next(); // tok should point to return or throw
|
|
for (const Token *tok2 = tok->next(); tok2; tok2 = tok2->next()) {
|
|
if (tok2->str() == "(" || tok2->str() == "{")
|
|
tok2 = tok2->link();
|
|
if (tok2->str() == ";") {
|
|
secondBreak = tok2->next();
|
|
break;
|
|
}
|
|
}
|
|
} else if (Token::Match(tok, "goto %any% ;")) {
|
|
secondBreak = tok->tokAt(3);
|
|
labelName = tok->next();
|
|
} else if (Token::Match(tok, "%var% (") && _settings->library.isnoreturn(tok->str()) && tok->strAt(-1) != ".") {
|
|
if ((!tok->function() || (tok->function()->token != tok && tok->function()->tokenDef != tok)) && tok->linkAt(1)->strAt(1) != "{")
|
|
secondBreak = tok->linkAt(1)->tokAt(2);
|
|
}
|
|
|
|
// Statements follow directly, no line between them. (#3383)
|
|
// TODO: Try to find a better way to avoid false positives due to preprocessor configurations.
|
|
bool inconclusive = secondBreak && (secondBreak->linenr() - 1 > secondBreak->previous()->linenr());
|
|
|
|
if (secondBreak && (_settings->inconclusive || !inconclusive)) {
|
|
if (Token::Match(secondBreak, "continue|goto|throw") ||
|
|
(secondBreak->str() == "return" && (tok->str() == "return" || secondBreak->strAt(1) == ";"))) { // return with value after statements like throw can be necessary to make a function compile
|
|
duplicateBreakError(secondBreak, inconclusive);
|
|
tok = Token::findmatch(secondBreak, "[}:]");
|
|
} else if (secondBreak->str() == "break") { // break inside switch as second break statement should not issue a warning
|
|
if (tok->str() == "break") // If the previous was a break, too: Issue warning
|
|
duplicateBreakError(secondBreak, inconclusive);
|
|
else {
|
|
if (tok->scope()->type != Scope::eSwitch) // Check, if the enclosing scope is a switch
|
|
duplicateBreakError(secondBreak, inconclusive);
|
|
}
|
|
tok = Token::findmatch(secondBreak, "[}:]");
|
|
} else if (!Token::Match(secondBreak, "return|}|case|default") && secondBreak->strAt(1) != ":") { // TODO: No bailout for unconditional scopes
|
|
// If the goto label is followed by a loop construct in which the label is defined it's quite likely
|
|
// that the goto jump was intended to skip some code on the first loop iteration.
|
|
bool labelInFollowingLoop = false;
|
|
if (labelName && Token::Match(secondBreak, "while|do|for")) {
|
|
const Token *scope2 = Token::findsimplematch(secondBreak, "{");
|
|
if (scope2) {
|
|
for (const Token *tokIter = scope2; tokIter != scope2->link() && tokIter; tokIter = tokIter->next()) {
|
|
if (Token::Match(tokIter, "[;{}] %any% :") && labelName->str() == tokIter->strAt(1)) {
|
|
labelInFollowingLoop = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!labelInFollowingLoop)
|
|
unreachableCodeError(secondBreak, inconclusive);
|
|
tok = Token::findmatch(secondBreak, "[}:]");
|
|
} else
|
|
tok = secondBreak;
|
|
|
|
if (!tok)
|
|
break;
|
|
tok = tok->previous(); // Will be advanced again by for loop
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::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.", inconclusive);
|
|
}
|
|
|
|
void CheckOther::unreachableCodeError(const Token *tok, bool inconclusive)
|
|
{
|
|
reportError(tok, Severity::style, "unreachableCode",
|
|
"Statements following return, break, continue, goto or throw will never be executed.", inconclusive);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// memset(p, y, 0 /* bytes to fill */) <- 2nd and 3rd arguments inverted
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkMemsetZeroBytes()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::simpleMatch(tok, "memset (")) {
|
|
const Token* lastParamTok = tok->next()->link()->previous();
|
|
if (lastParamTok->str() == "0")
|
|
memsetZeroBytesError(tok, tok->strAt(2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::memsetZeroBytesError(const Token *tok, const std::string &varname)
|
|
{
|
|
const std::string summary("memset() called to fill 0 bytes of '" + varname + "'.");
|
|
const std::string verbose(summary + " The second and third arguments might be inverted."
|
|
" The function memset ( void * ptr, int value, size_t num ) sets the"
|
|
" first num bytes of the block of memory pointed by ptr to the specified value.");
|
|
reportError(tok, Severity::warning, "memsetZeroBytes", summary + "\n" + verbose);
|
|
}
|
|
|
|
void CheckOther::checkMemsetInvalid2ndParam()
|
|
{
|
|
const bool portability = _settings->isEnabled("portability");
|
|
const bool warning = _settings->isEnabled("warning");
|
|
if (!warning && !portability)
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok && (tok != scope->classEnd); tok = tok->next()) {
|
|
if (Token::simpleMatch(tok, "memset (")) {
|
|
const Token* firstParamTok = tok->tokAt(2);
|
|
if (!firstParamTok)
|
|
continue;
|
|
const Token* secondParamTok = firstParamTok->nextArgument();
|
|
if (!secondParamTok)
|
|
continue;
|
|
|
|
// Second parameter is zero literal, i.e. 0.0f
|
|
if (Token::Match(secondParamTok, "%num% ,") && MathLib::isNullValue(secondParamTok->str()))
|
|
continue;
|
|
|
|
const Token *top = secondParamTok;
|
|
while (top->astParent() && top->astParent()->str() != ",")
|
|
top = top->astParent();
|
|
|
|
// Check if second parameter is a float variable or a float literal != 0.0f
|
|
if (portability && astIsFloat(top,false)) {
|
|
memsetFloatError(secondParamTok, top->expressionString());
|
|
} else if (warning && secondParamTok->isNumber()) { // Check if the second parameter is a literal and is out of range
|
|
const long long int value = MathLib::toLongNumber(secondParamTok->str());
|
|
if (value < -128 || value > 255)
|
|
memsetValueOutOfRangeError(secondParamTok, secondParamTok->str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::memsetFloatError(const Token *tok, const std::string &var_value)
|
|
{
|
|
const std::string message("The 2nd memset() argument '" + var_value +
|
|
"' is a float, its representation is implementation defined.");
|
|
const std::string verbose(message + " memset() is used to set each byte of a block of memory to a specific value and"
|
|
" the actual representation of a floating-point value is implementation defined.");
|
|
reportError(tok, Severity::portability, "memsetFloat", message + "\n" + verbose);
|
|
}
|
|
|
|
void CheckOther::memsetValueOutOfRangeError(const Token *tok, const std::string &value)
|
|
{
|
|
const std::string message("The 2nd memset() argument '" + value + "' doesn't fit into an 'unsigned char'.");
|
|
const std::string verbose(message + " The 2nd parameter is passed as an 'int', but the function fills the block of memory using the 'unsigned char' conversion of this value.");
|
|
reportError(tok, Severity::warning, "memsetValueOutOfRange", message + "\n" + verbose);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check scope of variables..
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkVariableScope()
|
|
{
|
|
if (!_settings->isEnabled("style"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
for (unsigned int i = 1; i < symbolDatabase->getVariableListSize(); i++) {
|
|
const Variable* var = symbolDatabase->getVariableFromVarId(i);
|
|
if (!var || !var->isLocal() || (!var->isPointer() && !var->typeStartToken()->isStandardType()))
|
|
continue;
|
|
|
|
if (var->isConst())
|
|
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;
|
|
} else if (tok->str() == "{" || tok->str() == ";" || tok->str() == "}")
|
|
break;
|
|
}
|
|
if (forHead)
|
|
continue;
|
|
|
|
const Token* tok = var->nameToken()->next();
|
|
if (Token::Match(tok, "; %varid% = %any% ;", var->declarationId())) {
|
|
tok = tok->tokAt(3);
|
|
if (!tok->isNumber() && tok->type() != Token::eString && tok->type() != Token::eChar && !tok->isBoolean())
|
|
continue;
|
|
}
|
|
bool reduce = true;
|
|
bool used = false; // Don't warn about unused variables
|
|
for (; tok && tok != var->scope()->classEnd; tok = tok->next()) {
|
|
if (tok->str() == "{" && tok->scope() != tok->previous()->scope() && !tok->isExpandedMacro() && tok->scope()->type != Scope::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 (") && Token::simpleMatch(tok->linkAt(3), ") {")) {
|
|
const Token *endif = tok->linkAt(3)->linkAt(1);
|
|
bool elseif = false;
|
|
if (Token::simpleMatch(endif, "} }"))
|
|
elseif = true;
|
|
else if (Token::simpleMatch(endif, "} else {") && Token::simpleMatch(endif->linkAt(2),"} }"))
|
|
elseif = true;
|
|
if (elseif && Token::findmatch(tok->next(), "%varid%", tok->linkAt(1), var->declarationId())) {
|
|
reduce = false;
|
|
break;
|
|
}
|
|
} else if (tok->varId() == var->declarationId() || tok->str() == "goto") {
|
|
reduce = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (reduce && used)
|
|
variableScopeError(var->nameToken(), var->name());
|
|
}
|
|
}
|
|
|
|
bool CheckOther::checkInnerScope(const Token *tok, const Variable* var, bool& used)
|
|
{
|
|
const Scope* scope = tok->next()->scope();
|
|
bool loopVariable = scope->type == Scope::eFor || scope->type == Scope::eWhile || scope->type == Scope::eDo;
|
|
bool noContinue = true;
|
|
const Token* forHeadEnd = 0;
|
|
const Token* end = tok->link();
|
|
if (scope->type == Scope::eUnconditional && (tok->strAt(-1) == ")" || tok->previous()->isName())) // Might be an unknown macro like BOOST_FOREACH
|
|
loopVariable = true;
|
|
|
|
if (scope->type == Scope::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 == Scope::eSwitch) {
|
|
for (std::list<Scope*>::const_iterator i = scope->nestedList.begin(); i != scope->nestedList.end(); ++i) {
|
|
if (used) {
|
|
bool used2 = false;
|
|
if (!checkInnerScope((*i)->classStart, var, used2) || used2) {
|
|
return false;
|
|
}
|
|
} else if (!checkInnerScope((*i)->classStart, var, used)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bFirstAssignment=false;
|
|
for (; 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 = 0;
|
|
|
|
if (loopVariable && noContinue && tok->scope() == scope && !forHeadEnd && scope->type != Scope::eSwitch && Token::Match(tok, "%varid% =", var->declarationId())) { // Assigned in outer scope.
|
|
loopVariable = false;
|
|
unsigned int indent = 0;
|
|
for (const Token* tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) { // Ensure that variable isn't used on right side of =, too
|
|
if (tok2->str() == "(")
|
|
indent++;
|
|
else if (tok2->str() == ")") {
|
|
if (indent == 0)
|
|
break;
|
|
indent--;
|
|
} else if (tok2->str() == ";")
|
|
break;
|
|
else if (tok2->varId() == var->declarationId()) {
|
|
loopVariable = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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% = %any%", var->declarationId()))
|
|
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())) // 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->type == Scope::eSwitch && scope == tok->scope())
|
|
return false; // Used in outer switch scope - unsafe or impossible to reduce scope
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CheckOther::variableScopeError(const Token *tok, const std::string &varname)
|
|
{
|
|
reportError(tok,
|
|
Severity::style,
|
|
"variableScope",
|
|
"The scope of the variable '" + varname + "' can be reduced.\n"
|
|
"The scope of the variable '" + varname + "' 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.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Comma in return statement: return a+1, b++;. (experimental)
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkCommaSeparatedReturn()
|
|
{
|
|
// This is experimental for now. See #5076
|
|
if (!_settings->experimental)
|
|
return;
|
|
|
|
if (!_settings->isEnabled("style"))
|
|
return;
|
|
|
|
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
|
|
if (tok->str() == "return") {
|
|
while (tok && tok->str() != ";") {
|
|
if (Token::Match(tok, "[([{<]") && tok->link())
|
|
tok = tok->link();
|
|
|
|
if (!tok->isExpandedMacro() && tok->str() == "," && tok->linenr() != tok->next()->linenr())
|
|
commaSeparatedReturnError(tok);
|
|
|
|
tok = tok->next();
|
|
}
|
|
// bailout: missing semicolon (invalid code / bad tokenizer)
|
|
if (!tok)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::commaSeparatedReturnError(const Token *tok)
|
|
{
|
|
reportError(tok,
|
|
Severity::style,
|
|
"commaSeparatedReturn",
|
|
"Comma is used in return statement. The comma can easily be misread as a ';'.\n"
|
|
"Comma is used in return statement. When comma is used in a return statement it can "
|
|
"easily be misread as a semicolon. For example in the code below the value "
|
|
"of 'b' is returned if the condition is true, but it is easy to think that 'a+1' is "
|
|
"returned:\n"
|
|
" if (x)\n"
|
|
" return a + 1,\n"
|
|
" b++;\n"
|
|
"However it can be useful to use comma in macros. Cppcheck does not warn when such a "
|
|
"macro is then used in a return statement, it is less likely such code is misunderstood.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check for constant function parameters
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkConstantFunctionParameter()
|
|
{
|
|
if (!_settings->isEnabled("performance") || _tokenizer->isC())
|
|
return;
|
|
|
|
const SymbolDatabase * const symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
for (unsigned int i = 1; i < symbolDatabase->getVariableListSize(); i++) {
|
|
const Variable* var = symbolDatabase->getVariableFromVarId(i);
|
|
if (!var || !var->isArgument() || !var->isClass() || !var->isConst() || var->isPointer() || var->isArray() || var->isReference())
|
|
continue;
|
|
|
|
if (var->scope() && var->scope()->function->arg->link()->strAt(-1) == ".")
|
|
continue; // references could not be used as va_start parameters (#5824)
|
|
|
|
const Token* const tok = var->typeStartToken();
|
|
// TODO: False negatives. This pattern only checks for string.
|
|
// Investigate if there are other classes in the std
|
|
// namespace and add them to the pattern. There are
|
|
// streams for example (however it seems strange with
|
|
// const stream parameter).
|
|
if (var->isStlStringType()) {
|
|
passedByValueError(tok, var->name());
|
|
} else if (var->isStlType() && Token::Match(tok, "std :: %type% <") && !Token::simpleMatch(tok->linkAt(3), "> ::")) {
|
|
passedByValueError(tok, var->name());
|
|
} else if (var->type()) { // Check if type is a struct or class.
|
|
passedByValueError(tok, var->name());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::passedByValueError(const Token *tok, const std::string &parname)
|
|
{
|
|
reportError(tok, Severity::performance, "passedByValue",
|
|
"Function parameter '" + parname + "' should be passed by reference.\n"
|
|
"Parameter '" + parname + "' is passed by value. It could be passed "
|
|
"as a (const) reference which is usually faster and recommended in C++.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check usage of char variables..
|
|
//---------------------------------------------------------------------------
|
|
static bool isChar(const Variable* var)
|
|
{
|
|
return (var && !var->isPointer() && !var->isArray() && var->typeStartToken()->str() == "char");
|
|
}
|
|
|
|
static bool isSignedChar(const Variable* var)
|
|
{
|
|
return (isChar(var) && !var->typeStartToken()->isUnsigned());
|
|
}
|
|
|
|
static bool astIsSignedChar(const Token *tok)
|
|
{
|
|
if (!tok)
|
|
return false;
|
|
if (tok->str() == "*" && tok->astOperand1() && !tok->astOperand2()) {
|
|
const Variable *var = tok->astOperand1()->variable();
|
|
if (!var || !var->isPointer())
|
|
return false;
|
|
const Token *type = var->typeStartToken();
|
|
while (type && type->str() == "const")
|
|
type = type->next();
|
|
return (type && type->str() == "char" && !type->isUnsigned());
|
|
}
|
|
return isSignedChar(tok->variable());
|
|
}
|
|
|
|
void CheckOther::checkCharVariable()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::Match(tok, "%var% [") && astIsSignedChar(tok->next()->astOperand2())) {
|
|
const Variable* arrayvar = tok->variable();
|
|
const MathLib::bigint arraysize = (arrayvar && arrayvar->isArray()) ? arrayvar->dimension(0U) : 0;
|
|
if (arraysize > 0x80)
|
|
charArrayIndexError(tok);
|
|
}
|
|
|
|
else if (Token::Match(tok, "[&|^]")) {
|
|
const Token *tok2;
|
|
if (tok->astOperand1() && astIsSignedChar(tok->astOperand1()))
|
|
tok2 = tok->astOperand2();
|
|
else if (tok->astOperand2() && astIsSignedChar(tok->astOperand2()))
|
|
tok2 = tok->astOperand1();
|
|
else
|
|
continue;
|
|
|
|
// Don't care about address-of operator
|
|
if (!tok->astOperand2())
|
|
continue;
|
|
|
|
// it's ok with a bitwise and where the other operand is 0xff or less..
|
|
if (tok->str() == "&" && tok2 && tok2->isNumber() && MathLib::isGreater("0x100", tok2->str()))
|
|
continue;
|
|
|
|
// is the result stored in a short|int|long?
|
|
if (tok->astParent() && tok->astParent()->str() == "=") {
|
|
const Token *eq = tok->astParent();
|
|
const Token *lhs = eq->astOperand1();
|
|
if (lhs && lhs->str() == "*" && !lhs->astOperand2())
|
|
lhs = lhs->astOperand1();
|
|
while (lhs && (lhs->str() == "." || lhs->str() == "::"))
|
|
lhs = lhs->astOperand2();
|
|
if (!lhs || !lhs->isName())
|
|
continue;
|
|
const Variable *var = lhs->variable();
|
|
if (var && var->isIntegralType() && var->typeStartToken()->str() != "char")
|
|
charBitOpError(tok); // This is an error..
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::charArrayIndexError(const Token *tok)
|
|
{
|
|
reportError(tok,
|
|
Severity::warning,
|
|
"charArrayIndex",
|
|
"Signed 'char' type used as array index.\n"
|
|
"Signed 'char' type used as array index. If the value "
|
|
"can be greater than 127 there will be a buffer underflow "
|
|
"because of sign extension.");
|
|
}
|
|
|
|
void CheckOther::charBitOpError(const Token *tok)
|
|
{
|
|
reportError(tok,
|
|
Severity::warning,
|
|
"charBitOp",
|
|
"When using 'char' variables in bit operations, sign extension can generate unexpected results.\n"
|
|
"When using 'char' variables in bit operations, sign extension can generate unexpected results. For example:\n"
|
|
" char c = 0x80;\n"
|
|
" int i = 0 | c;\n"
|
|
" if (i & 0x8000)\n"
|
|
" printf(\"not expected\");\n"
|
|
"The \"not expected\" will be printed on the screen.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Incomplete statement..
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkIncompleteStatement()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
|
|
if (tok->str() == "(") {
|
|
tok = tok->link();
|
|
if (Token::simpleMatch(tok, ") {") && Token::simpleMatch(tok->next()->link(), "} ;"))
|
|
tok = tok->next()->link();
|
|
}
|
|
|
|
else if (Token::simpleMatch(tok, "= {"))
|
|
tok = tok->next()->link();
|
|
|
|
// C++11 struct/array/etc initialization in initializer list
|
|
else if (Token::Match(tok->previous(), "%var% {") && !Token::findsimplematch(tok,";",tok->link()))
|
|
tok = tok->link();
|
|
|
|
// C++11 vector initialization / return { .. }
|
|
else if (Token::Match(tok,"> %var% {") || Token::Match(tok, "[;{}] return {"))
|
|
tok = tok->linkAt(2);
|
|
|
|
// C++11 initialize set in initalizer list : [,:] std::set<int>{1} [{,]
|
|
else if (Token::simpleMatch(tok,"> {") && tok->link())
|
|
tok = tok->next()->link();
|
|
|
|
else if (Token::Match(tok, "[;{}] %str%|%num%")) {
|
|
// No warning if numeric constant is followed by a "." or ","
|
|
if (Token::Match(tok->next(), "%num% [,.]"))
|
|
continue;
|
|
|
|
// bailout if there is a "? :" in this statement
|
|
bool bailout = false;
|
|
for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) {
|
|
if (tok2->str() == "?")
|
|
bailout = true;
|
|
else if (tok2->str() == ";")
|
|
break;
|
|
}
|
|
if (bailout)
|
|
continue;
|
|
|
|
// no warning if this is the last statement in a ({})
|
|
for (const Token *tok2 = tok->next(); tok2; tok2 = tok2->next()) {
|
|
if (tok2->str() == "(")
|
|
tok2 = tok2->link();
|
|
else if (Token::Match(tok2, "[;{}]")) {
|
|
bailout = Token::simpleMatch(tok2, "; } )");
|
|
break;
|
|
}
|
|
}
|
|
if (bailout)
|
|
continue;
|
|
|
|
constStatementError(tok->next(), tok->next()->isNumber() ? "numeric" : "string");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::constStatementError(const Token *tok, const std::string &type)
|
|
{
|
|
reportError(tok, Severity::warning, "constStatement", "Redundant code: Found a statement that begins with " + type + " constant.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Detect division by zero.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkZeroDivision()
|
|
{
|
|
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
|
|
if (Token::Match(tok, "div|ldiv|lldiv|imaxdiv ( %num% , %num% )") &&
|
|
MathLib::isInt(tok->strAt(4)) &&
|
|
MathLib::toLongNumber(tok->strAt(4)) == 0L) {
|
|
if (tok->str() == "div") {
|
|
if (tok->strAt(-1) == ".")
|
|
continue;
|
|
if (tok->variable() || tok->function())
|
|
continue;
|
|
}
|
|
zerodivError(tok,false);
|
|
} else if (Token::Match(tok, "[/%]") && tok->astOperand2()) {
|
|
// Value flow..
|
|
const ValueFlow::Value *value = tok->astOperand2()->getValue(0LL);
|
|
if (value) {
|
|
if (!_settings->inconclusive && value->inconclusive)
|
|
continue;
|
|
if (value->condition == nullptr)
|
|
zerodivError(tok, value->inconclusive);
|
|
else if (_settings->isEnabled("warning"))
|
|
zerodivcondError(value->condition,tok,value->inconclusive);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::zerodivError(const Token *tok, bool inconclusive)
|
|
{
|
|
reportError(tok, Severity::error, "zerodiv", "Division by zero.", inconclusive);
|
|
}
|
|
|
|
void CheckOther::zerodivcondError(const Token *tokcond, const Token *tokdiv, bool inconclusive)
|
|
{
|
|
std::list<const Token *> callstack;
|
|
while (Token::Match(tokcond, "(|%oror%|&&"))
|
|
tokcond = tokcond->next();
|
|
if (tokcond && tokdiv) {
|
|
callstack.push_back(tokcond);
|
|
callstack.push_back(tokdiv);
|
|
}
|
|
std::string condition;
|
|
if (!tokcond) {
|
|
// getErrorMessages
|
|
} else if (Token::Match(tokcond, "%num% <|<=")) {
|
|
condition = tokcond->strAt(2) + ((tokcond->strAt(1) == "<") ? ">" : ">=") + tokcond->str();
|
|
} else if (tokcond->isComparisonOp()) {
|
|
condition = tokcond->expressionString();
|
|
} else {
|
|
if (tokcond->str() == "!")
|
|
condition = tokcond->next()->str() + "==0";
|
|
else
|
|
condition = tokcond->str() + "!=0";
|
|
}
|
|
const std::string linenr(MathLib::toString(tokdiv ? tokdiv->linenr() : 0));
|
|
reportError(callstack, Severity::warning, "zerodivcond", "Either the condition '"+condition+"' is useless or there is division by zero at line " + linenr + ".", inconclusive);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check for NaN (not-a-number) in an arithmetic expression, e.g.
|
|
// double d = 1.0 / 0.0 + 100.0;
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CheckOther::checkNanInArithmeticExpression()
|
|
{
|
|
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
|
|
if (Token::Match(tok, "inf.0 +|-") ||
|
|
Token::Match(tok, "+|- inf.0") ||
|
|
Token::Match(tok, "+|- %num% / 0.0")) {
|
|
nanInArithmeticExpressionError(tok);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::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.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Detect passing wrong values to <cmath> functions like atan(0, x);
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkMathFunctions()
|
|
{
|
|
bool styleC99 = _settings->isEnabled("style") && _settings->standards.c != Standards::C89 && _settings->standards.cpp != Standards::CPP03;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (tok->varId())
|
|
continue;
|
|
if (tok->strAt(-1) != "."
|
|
&& Token::Match(tok, "log|logf|logl|log10|log10f|log10l ( %num% )")) {
|
|
bool isNegative = MathLib::isNegative(tok->strAt(2));
|
|
bool isInt = MathLib::isInt(tok->strAt(2));
|
|
bool isFloat = MathLib::isFloat(tok->strAt(2));
|
|
if (isNegative && isInt && MathLib::toLongNumber(tok->strAt(2)) <= 0) {
|
|
mathfunctionCallWarning(tok); // case log(-2)
|
|
} else if (isNegative && isFloat && MathLib::toDoubleNumber(tok->strAt(2)) <= 0.) {
|
|
mathfunctionCallWarning(tok); // case log(-2.0)
|
|
} else if (!isNegative && isFloat && MathLib::toDoubleNumber(tok->strAt(2)) <= 0.) {
|
|
mathfunctionCallWarning(tok); // case log(0.0)
|
|
} else if (!isNegative && isInt && MathLib::toLongNumber(tok->strAt(2)) <= 0) {
|
|
mathfunctionCallWarning(tok); // case log(0)
|
|
}
|
|
}
|
|
|
|
// acos( x ), asin( x ) where x is defined for interval [-1,+1], but not beyond
|
|
else if (Token::Match(tok, "acos|acosl|acosf|asin|asinf|asinl ( %num% )") &&
|
|
std::fabs(MathLib::toDoubleNumber(tok->strAt(2))) > 1.0) {
|
|
mathfunctionCallWarning(tok);
|
|
}
|
|
// sqrt( x ): if x is negative the result is undefined
|
|
else if (Token::Match(tok, "sqrt|sqrtf|sqrtl ( %num% )") &&
|
|
MathLib::isNegative(tok->strAt(2))) {
|
|
mathfunctionCallWarning(tok);
|
|
}
|
|
// atan2 ( x , y): x and y can not be zero, because this is mathematically not defined
|
|
else if (Token::Match(tok, "atan2|atan2f|atan2l ( %num% , %num% )") &&
|
|
MathLib::isNullValue(tok->strAt(2)) &&
|
|
MathLib::isNullValue(tok->strAt(4))) {
|
|
mathfunctionCallWarning(tok, 2);
|
|
}
|
|
// fmod ( x , y) If y is zero, then either a range error will occur or the function will return zero (implementation-defined).
|
|
else if (Token::Match(tok, "fmod|fmodf|fmodl ( %any%")) {
|
|
const Token* nextArg = tok->tokAt(2)->nextArgument();
|
|
if (nextArg && nextArg->isNumber() && MathLib::isNullValue(nextArg->str()))
|
|
mathfunctionCallWarning(tok, 2);
|
|
}
|
|
// pow ( x , y) If x is zero, and y is negative --> division by zero
|
|
else if (Token::Match(tok, "pow|powf|powl ( %num% , %num% )") &&
|
|
MathLib::isNullValue(tok->strAt(2)) &&
|
|
MathLib::isNegative(tok->strAt(4))) {
|
|
mathfunctionCallWarning(tok, 2);
|
|
}
|
|
|
|
if (styleC99) {
|
|
if (Token::Match(tok, "%num% - erf (") && Tokenizer::isOneNumber(tok->str()) && tok->next()->astOperand2() == tok->tokAt(3)) {
|
|
mathfunctionCallWarning(tok, "1 - erf(x)", "erfc(x)");
|
|
} else if (Token::simpleMatch(tok, "exp (") && Token::Match(tok->linkAt(1), ") - %num%") && Tokenizer::isOneNumber(tok->linkAt(1)->strAt(2)) && tok->linkAt(1)->next()->astOperand1() == tok->next()) {
|
|
mathfunctionCallWarning(tok, "exp(x) - 1", "expm1(x)");
|
|
} else if (Token::simpleMatch(tok, "log (") && tok->next()->astOperand2()) {
|
|
const Token* plus = tok->next()->astOperand2();
|
|
if (plus->str() == "+" && ((plus->astOperand1() && Tokenizer::isOneNumber(plus->astOperand1()->str())) || (plus->astOperand2() && Tokenizer::isOneNumber(plus->astOperand2()->str()))))
|
|
mathfunctionCallWarning(tok, "log(1 + x)", "log1p(x)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::mathfunctionCallWarning(const Token *tok, const unsigned int numParam)
|
|
{
|
|
if (tok) {
|
|
if (numParam == 1)
|
|
reportError(tok, Severity::warning, "wrongmathcall", "Passing value " + tok->strAt(2) + " to " + tok->str() + "() leads to implementation-defined result.");
|
|
else if (numParam == 2)
|
|
reportError(tok, Severity::warning, "wrongmathcall", "Passing values " + tok->strAt(2) + " and " + tok->strAt(4) + " to " + tok->str() + "() leads to implementation-defined result.");
|
|
} else
|
|
reportError(tok, Severity::warning, "wrongmathcall", "Passing value '#' to #() leads to implementation-defined result.");
|
|
}
|
|
|
|
void CheckOther::mathfunctionCallWarning(const Token *tok, const std::string& oldexp, const std::string& newexp)
|
|
{
|
|
reportError(tok, Severity::style, "unpreciseMathCall", "Expression '" + oldexp + "' can be replaced by '" + newexp + "' to avoid loss of precision.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Creating instance of clases which are destroyed immediately
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkMisusedScopedObject()
|
|
{
|
|
// Skip this check for .c files
|
|
if (_tokenizer->isC()) {
|
|
return;
|
|
}
|
|
|
|
const SymbolDatabase * const symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token *tok = scope->classStart; tok && tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::Match(tok, "[;{}] %var% (")
|
|
&& Token::Match(tok->linkAt(2), ") ; !!}")
|
|
&& symbolDatabase->isClassOrStruct(tok->next()->str())
|
|
&& (!tok->next()->function() || // is not a function on this scope
|
|
(tok->next()->function() && tok->next()->function()->isConstructor()))) { // or is function in this scope and it's a ctor
|
|
tok = tok->next();
|
|
misusedScopeObjectError(tok, tok->str());
|
|
tok = tok->next();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::misusedScopeObjectError(const Token *tok, const std::string& varname)
|
|
{
|
|
reportError(tok, Severity::error,
|
|
"unusedScopedObject", "Instance of '" + varname + "' object is destroyed immediately.");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// check for duplicate code in if and else branches
|
|
// if (a) { b = true; } else { b = true; }
|
|
//-----------------------------------------------------------------------------
|
|
void CheckOther::checkDuplicateBranch()
|
|
{
|
|
// This is inconclusive since in practice most warnings are noise:
|
|
// * There can be unfixed low-priority todos. The code is fine as it
|
|
// is but it could be possible to enhance it. Writing a warning
|
|
// here is noise since the code is fine (see cppcheck, abiword, ..)
|
|
// * There can be overspecified code so some conditions can't be true
|
|
// and their conditional code is a duplicate of the condition that
|
|
// is always true just in case it would be false. See for instance
|
|
// abiword.
|
|
if (!_settings->isEnabled("style") || !_settings->inconclusive)
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
std::list<Scope>::const_iterator scope;
|
|
|
|
for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) {
|
|
if (scope->type != Scope::eIf)
|
|
continue;
|
|
|
|
// check all the code in the function for if (..) else
|
|
if (Token::simpleMatch(scope->classEnd, "} else {")) {
|
|
// Make sure there are no macros (different macros might be expanded
|
|
// to the same code)
|
|
bool macro = false;
|
|
for (const Token *tok = scope->classStart; tok != scope->classEnd->linkAt(2); tok = tok->next()) {
|
|
if (tok->isExpandedMacro()) {
|
|
macro = true;
|
|
break;
|
|
}
|
|
}
|
|
if (macro)
|
|
continue;
|
|
|
|
// save if branch code
|
|
std::string branch1 = scope->classStart->next()->stringifyList(scope->classEnd);
|
|
|
|
// save else branch code
|
|
std::string branch2 = scope->classEnd->tokAt(3)->stringifyList(scope->classEnd->linkAt(2));
|
|
|
|
// check for duplicates
|
|
if (!branch1.empty() && branch1 == branch2)
|
|
duplicateBranchError(scope->classDef, scope->classEnd->next());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::duplicateBranchError(const Token *tok1, const Token *tok2)
|
|
{
|
|
std::list<const Token *> toks;
|
|
toks.push_back(tok2);
|
|
toks.push_back(tok1);
|
|
|
|
reportError(toks, Severity::style, "duplicateBranch", "Found duplicate branches for 'if' and 'else'.\n"
|
|
"Finding the same code in an 'if' and related 'else' branch is suspicious and "
|
|
"might indicate a cut and paste or logic error. Please examine this code "
|
|
"carefully to determine if it is correct.", true);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Check for a free() of an invalid address
|
|
// char* p = malloc(100);
|
|
// free(p + 10);
|
|
//-----------------------------------------------------------------------------
|
|
void CheckOther::checkInvalidFree()
|
|
{
|
|
std::map<unsigned int, bool> allocatedVariables;
|
|
|
|
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
|
|
// Keep track of which variables were assigned addresses to newly-allocated memory
|
|
if (Token::Match(tok, "%var% = malloc|g_malloc|new")) {
|
|
allocatedVariables.insert(std::make_pair(tok->varId(), false));
|
|
}
|
|
|
|
// If a previously-allocated pointer is incremented or decremented, any subsequent
|
|
// free involving pointer arithmetic may or may not be invalid, so we should only
|
|
// report an inconclusive result.
|
|
else if (Token::Match(tok, "%var% = %var% +|-") &&
|
|
tok->varId() == tok->tokAt(2)->varId() &&
|
|
allocatedVariables.find(tok->varId()) != allocatedVariables.end()) {
|
|
if (_settings->inconclusive)
|
|
allocatedVariables[tok->varId()] = true;
|
|
else
|
|
allocatedVariables.erase(tok->varId());
|
|
}
|
|
|
|
// If a previously-allocated pointer is assigned a completely new value,
|
|
// we can't know if any subsequent free() on that pointer is valid or not.
|
|
else if (Token::Match(tok, "%var% =")) {
|
|
allocatedVariables.erase(tok->varId());
|
|
}
|
|
|
|
// If a variable that was previously assigned a newly-allocated memory location is
|
|
// added or subtracted from when used to free the memory, report an error.
|
|
else if (Token::Match(tok, "free|g_free|delete ( %any% +|- %any%") ||
|
|
Token::Match(tok, "delete [ ] ( %any% +|- %any%") ||
|
|
Token::Match(tok, "delete %any% +|- %any%")) {
|
|
|
|
const int varIndex = tok->strAt(1) == "(" ? 2 :
|
|
tok->strAt(3) == "(" ? 4 : 1;
|
|
const unsigned int var1 = tok->tokAt(varIndex)->varId();
|
|
const unsigned int var2 = tok->tokAt(varIndex + 2)->varId();
|
|
const std::map<unsigned int, bool>::iterator alloc1 = allocatedVariables.find(var1);
|
|
const std::map<unsigned int, bool>::iterator alloc2 = allocatedVariables.find(var2);
|
|
if (alloc1 != allocatedVariables.end()) {
|
|
invalidFreeError(tok, alloc1->second);
|
|
} else if (alloc2 != allocatedVariables.end()) {
|
|
invalidFreeError(tok, alloc2->second);
|
|
}
|
|
}
|
|
|
|
// If the previously-allocated variable is passed in to another function
|
|
// as a parameter, it might be modified, so we shouldn't report an error
|
|
// if it is later used to free memory
|
|
else if (Token::Match(tok, "%var% (") && _settings->library.functionpure.find(tok->str()) == _settings->library.functionpure.end()) {
|
|
const Token* tok2 = Token::findmatch(tok->next(), "%var%", tok->linkAt(1));
|
|
while (tok2 != nullptr) {
|
|
allocatedVariables.erase(tok2->varId());
|
|
tok2 = Token::findmatch(tok2->next(), "%var%", tok->linkAt(1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::invalidFreeError(const Token *tok, bool inconclusive)
|
|
{
|
|
reportError(tok, Severity::error, "invalidFree", "Invalid memory address freed.", inconclusive);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Check for double free
|
|
// free(p); free(p);
|
|
//-----------------------------------------------------------------------------
|
|
void CheckOther::checkDoubleFree()
|
|
{
|
|
std::set<unsigned int> freedVariables;
|
|
std::set<unsigned int> closeDirVariables;
|
|
|
|
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
freedVariables.clear();
|
|
closeDirVariables.clear();
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
// Keep track of any variables passed to "free()", "g_free()" or "closedir()",
|
|
// and report an error if the same variable is passed twice.
|
|
if (Token::Match(tok, "free|g_free|closedir ( %var% )")) {
|
|
const unsigned int varId = tok->tokAt(2)->varId();
|
|
if (varId) {
|
|
if (Token::Match(tok, "free|g_free")) {
|
|
if (freedVariables.find(varId) != freedVariables.end())
|
|
doubleFreeError(tok, tok->strAt(2));
|
|
else
|
|
freedVariables.insert(varId);
|
|
} else if (tok->str() == "closedir") {
|
|
if (closeDirVariables.find(varId) != closeDirVariables.end())
|
|
doubleCloseDirError(tok, tok->strAt(2));
|
|
else
|
|
closeDirVariables.insert(varId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep track of any variables operated on by "delete" or "delete[]"
|
|
// and report an error if the same variable is delete'd twice.
|
|
else if (Token::Match(tok, "delete %var% ;") || Token::Match(tok, "delete [ ] %var% ;")) {
|
|
const int varIndex = (tok->strAt(1) == "[") ? 3 : 1;
|
|
const unsigned int varId = tok->tokAt(varIndex)->varId();
|
|
if (varId) {
|
|
if (freedVariables.find(varId) != freedVariables.end())
|
|
doubleFreeError(tok, tok->strAt(varIndex));
|
|
else
|
|
freedVariables.insert(varId);
|
|
}
|
|
}
|
|
|
|
// If this scope doesn't return, clear the set of previously freed variables
|
|
else if (tok->str() == "}" && _tokenizer->IsScopeNoReturn(tok)) {
|
|
freedVariables.clear();
|
|
closeDirVariables.clear();
|
|
}
|
|
|
|
// If this scope is a "for" or "while" loop that contains "break" or "continue",
|
|
// give up on trying to figure out the flow of execution and just clear the set
|
|
// of previously freed variables.
|
|
// TODO: There are false negatives. This bailout is only needed when the
|
|
// loop will exit without free()'ing the memory on the last iteration.
|
|
else if (tok->str() == "}" && tok->link() && tok->link()->previous() &&
|
|
tok->link()->linkAt(-1) &&
|
|
Token::Match(tok->link()->linkAt(-1)->previous(), "while|for") &&
|
|
Token::findmatch(tok->link()->linkAt(-1), "break|continue ;", tok) != nullptr) {
|
|
freedVariables.clear();
|
|
closeDirVariables.clear();
|
|
}
|
|
|
|
// If a variable is passed to a function, remove it from the set of previously freed variables
|
|
else if (Token::Match(tok, "%var% (") && _settings->library.leakignore.find(tok->str()) == _settings->library.leakignore.end()) {
|
|
|
|
// If this is a new function definition, clear all variables
|
|
if (Token::simpleMatch(tok->next()->link(), ") {")) {
|
|
freedVariables.clear();
|
|
closeDirVariables.clear();
|
|
}
|
|
// If it is a function call, then clear those variables in its argument list
|
|
else if (Token::simpleMatch(tok->next()->link(), ") ;")) {
|
|
for (const Token* tok2 = tok->tokAt(2); tok2 != tok->linkAt(1); tok2 = tok2->next()) {
|
|
const unsigned int varId = tok2->varId();
|
|
if (varId) {
|
|
freedVariables.erase(varId);
|
|
closeDirVariables.erase(varId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If a pointer is assigned a new value, remove it from the set of previously freed variables
|
|
else if (Token::Match(tok, "%var% =")) {
|
|
const unsigned int varId = tok->varId();
|
|
if (varId) {
|
|
freedVariables.erase(varId);
|
|
closeDirVariables.erase(varId);
|
|
}
|
|
}
|
|
|
|
// Any control statements in-between delete, free() or closedir() statements
|
|
// makes it unclear whether any subsequent statements would be redundant.
|
|
if (Token::Match(tok, "if|else|for|while|break|continue|goto|return|throw|switch")) {
|
|
freedVariables.clear();
|
|
closeDirVariables.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::doubleFreeError(const Token *tok, const std::string &varname)
|
|
{
|
|
reportError(tok, Severity::error, "doubleFree", "Memory pointed to by '" + varname +"' is freed twice.");
|
|
}
|
|
|
|
void CheckOther::doubleCloseDirError(const Token *tok, const std::string &varname)
|
|
{
|
|
reportError(tok, Severity::error, "doubleCloseDir", "Directory handle '" + varname +"' closed twice.");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// check for the same expression on both sides of an operator
|
|
// (x == x), (x && x), (x || x)
|
|
// (x.y == x.y), (x.y && x.y), (x.y || x.y)
|
|
//---------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
bool notconst(const Function* func)
|
|
{
|
|
return !func->isConst;
|
|
}
|
|
|
|
void getConstFunctions(const SymbolDatabase *symbolDatabase, std::list<const Function*> &constFunctions)
|
|
{
|
|
std::list<Scope>::const_iterator scope;
|
|
for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) {
|
|
std::list<Function>::const_iterator func;
|
|
// only add const functions that do not have a non-const overloaded version
|
|
// since it is pretty much impossible to tell which is being called.
|
|
typedef std::map<std::string, std::list<const Function*> > StringFunctionMap;
|
|
StringFunctionMap functionsByName;
|
|
for (func = scope->functionList.begin(); func != scope->functionList.end(); ++func) {
|
|
functionsByName[func->tokenDef->str()].push_back(&*func);
|
|
}
|
|
for (StringFunctionMap::iterator it = functionsByName.begin();
|
|
it != functionsByName.end(); ++it) {
|
|
std::list<const Function*>::const_iterator nc = std::find_if(it->second.begin(), it->second.end(), notconst);
|
|
if (nc == it->second.end()) {
|
|
// ok to add all of them
|
|
constFunctions.splice(constFunctions.end(), it->second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool isWithoutSideEffects(const Tokenizer *tokenizer, const Token* tok)
|
|
{
|
|
if (!tokenizer->isCPP())
|
|
return true;
|
|
|
|
while (tok && tok->astOperand2() && tok->astOperand2()->str() != "(")
|
|
tok = tok->astOperand2();
|
|
if (tok && tok->varId()) {
|
|
const Variable* var = tok->variable();
|
|
return var && (!var->isClass() || var->isPointer() || var->isStlType());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CheckOther::checkDuplicateExpression()
|
|
{
|
|
if (!_settings->isEnabled("style"))
|
|
return;
|
|
|
|
// Parse all executing scopes..
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
std::list<Scope>::const_iterator scope;
|
|
std::list<const Function*> constFunctions;
|
|
getConstFunctions(symbolDatabase, constFunctions);
|
|
|
|
for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) {
|
|
// only check functions
|
|
if (scope->type != Scope::eFunction)
|
|
continue;
|
|
|
|
for (const Token *tok = scope->classStart; tok && tok != scope->classEnd; tok = tok->next()) {
|
|
if (tok->isOp() && tok->astOperand1() && !Token::Match(tok, "+|*|<<|>>")) {
|
|
if (Token::Match(tok, "==|!=|-") && astIsFloat(tok->astOperand1(), true))
|
|
continue;
|
|
if (isSameExpression(tok->astOperand1(), tok->astOperand2(), _settings->library.functionpure)) {
|
|
if (isWithoutSideEffects(_tokenizer, tok->astOperand1())) {
|
|
const bool assignment = tok->str() == "=";
|
|
if (assignment)
|
|
selfAssignmentError(tok, tok->astOperand1()->expressionString());
|
|
else
|
|
duplicateExpressionError(tok, tok, tok->str());
|
|
}
|
|
} else if (!Token::Match(tok, "[-/%]")) { // These operators are not associative
|
|
if (tok->astOperand2() && tok->str() == tok->astOperand1()->str() && isSameExpression(tok->astOperand2(), tok->astOperand1()->astOperand2(), _settings->library.functionpure) && isWithoutSideEffects(_tokenizer, tok->astOperand2()))
|
|
duplicateExpressionError(tok->astOperand2(), tok->astOperand2(), tok->str());
|
|
else if (tok->astOperand2()) {
|
|
const Token *ast1 = tok->astOperand1();
|
|
while (ast1 && tok->str() == ast1->str()) {
|
|
if (isSameExpression(ast1->astOperand1(), tok->astOperand2(), _settings->library.functionpure) && isWithoutSideEffects(_tokenizer, ast1->astOperand1()))
|
|
// TODO: warn if variables are unchanged. See #5683
|
|
// Probably the message should be changed to 'duplicate expressions X in condition or something like that'.
|
|
;//duplicateExpressionError(ast1->astOperand1(), tok->astOperand2(), tok->str());
|
|
else if (isSameExpression(ast1->astOperand2(), tok->astOperand2(), _settings->library.functionpure) && isWithoutSideEffects(_tokenizer, ast1->astOperand2()))
|
|
duplicateExpressionError(ast1->astOperand2(), tok->astOperand2(), tok->str());
|
|
if (!isConstExpression(ast1->astOperand2(), _settings->library.functionpure))
|
|
break;
|
|
ast1 = ast1->astOperand1();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::duplicateExpressionError(const Token *tok1, const Token *tok2, const std::string &op)
|
|
{
|
|
std::list<const Token *> toks;
|
|
toks.push_back(tok2);
|
|
toks.push_back(tok1);
|
|
|
|
reportError(toks, Severity::style, "duplicateExpression", "Same expression on both sides of \'" + op + "\'.\n"
|
|
"Finding the same expression on both sides of an operator is suspicious and might "
|
|
"indicate a cut and paste or logic error. Please examine this code carefully to "
|
|
"determine if it is correct.");
|
|
}
|
|
|
|
void CheckOther::selfAssignmentError(const Token *tok, const std::string &varname)
|
|
{
|
|
reportError(tok, Severity::warning,
|
|
"selfAssignment", "Redundant assignment of '" + varname + "' to itself.");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Check is a comparison of two variables leads to condition, which is
|
|
// always true or false.
|
|
// For instance: int a = 1; if(isless(a,a)){...}
|
|
// In this case isless(a,a) evaluates always to false.
|
|
//
|
|
// Reference:
|
|
// - http://www.cplusplus.com/reference/cmath/
|
|
//-----------------------------------------------------------------------------
|
|
void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (tok->isName() && Token::Match(tok, "isgreater|isless|islessgreater|isgreaterequal|islessequal ( %var% , %var% )")) {
|
|
const unsigned int varidLeft = tok->tokAt(2)->varId();// get the left varid
|
|
const unsigned int varidRight = tok->tokAt(4)->varId();// get the right varid
|
|
// compare varids: if they are not zero but equal
|
|
// --> the comparison function is calles with the same variables
|
|
if (varidLeft != 0 && varidLeft == varidRight) {
|
|
const std::string& functionName = tok->str(); // store function name
|
|
const std::string& varNameLeft = tok->strAt(2); // get the left variable name
|
|
if (functionName == "isgreater" || functionName == "isless" || functionName == "islessgreater") {
|
|
// e.g.: isgreater(x,x) --> (x)>(x) --> false
|
|
checkComparisonFunctionIsAlwaysTrueOrFalseError(tok, functionName, varNameLeft, false);
|
|
} else { // functionName == "isgreaterequal" || functionName == "islessequal"
|
|
// e.g.: isgreaterequal(x,x) --> (x)>=(x) --> true
|
|
checkComparisonFunctionIsAlwaysTrueOrFalseError(tok, functionName, varNameLeft, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalseError(const Token* tok, const std::string &functionName, const std::string &varName, const bool result)
|
|
{
|
|
const std::string strResult = result ? "true" : "false";
|
|
reportError(tok, Severity::warning, "comparisonFunctionIsAlwaysTrueOrFalse",
|
|
"Comparison of two identical variables with " + functionName + "(" + varName + "," + varName + ") evaluates always to " + strResult + ".\n"
|
|
"The function " + functionName + " is designed to compare two variables. Calling this function with one variable (" + varName + ") "
|
|
"for both parameters leads to a statement which is always " + strResult + ".");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Check for code like:
|
|
// seteuid(geteuid()) or setuid(getuid()), which first gets and then sets the
|
|
// (effective) user id to itself. Very often this indicates a copy and paste
|
|
// error.
|
|
//-----------------------------------------------------------------------------
|
|
void CheckOther::redundantGetAndSetUserId()
|
|
{
|
|
if (!_settings->standards.posix || !_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
// check all the code in the function
|
|
for (const Token *tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::simpleMatch(tok, "setuid ( getuid ( ) )")
|
|
|| Token::simpleMatch(tok, "seteuid ( geteuid ( ) )")
|
|
|| Token::simpleMatch(tok, "setgid ( getgid ( ) )")
|
|
|| Token::simpleMatch(tok, "setegid ( getegid ( ) )")) {
|
|
redundantGetAndSetUserIdError(tok);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void CheckOther::redundantGetAndSetUserIdError(const Token *tok)
|
|
{
|
|
reportError(tok, Severity::warning,
|
|
"redundantGetAndSetUserId", "Redundant get and set of user id.\n"
|
|
"Redundant statement without any effect. First the user id is retrieved"
|
|
"by get(e)uid() and then set with set(e)uid().", false);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check testing sign of unsigned variables and pointers.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkSignOfUnsignedVariable()
|
|
{
|
|
if (!_settings->isEnabled("style"))
|
|
return;
|
|
|
|
const bool inconclusive = _tokenizer->codeWithTemplates();
|
|
if (inconclusive && !_settings->inconclusive)
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
// check all the code in the function
|
|
for (const Token *tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::Match(tok, "%var% <|<= 0") && tok->varId() && !Token::Match(tok->tokAt(3), "+|-")) {
|
|
// TODO: handle a[10].b , a::b , (unsigned int)x , etc
|
|
const Token *prev = tok->previous();
|
|
while (prev && (prev->isName() || prev->str() == "."))
|
|
prev = prev->previous();
|
|
if (!Token::Match(prev, "(|&&|%oror%"))
|
|
continue;
|
|
const Variable *var = tok->variable();
|
|
if (var && var->typeEndToken()->isUnsigned())
|
|
unsignedLessThanZeroError(tok, var->name(), inconclusive);
|
|
else if (var && (var->isPointer() || var->isArray()))
|
|
pointerLessThanZeroError(tok, inconclusive);
|
|
} else if (Token::Match(tok, "0 >|>= %var%") && tok->tokAt(2)->varId() && !Token::Match(tok->tokAt(3), "+|-|*|/") && !Token::Match(tok->previous(), "+|-|<<|>>|~")) {
|
|
const Variable *var = tok->tokAt(2)->variable();
|
|
if (var && var->typeEndToken()->isUnsigned())
|
|
unsignedLessThanZeroError(tok, var->name(), inconclusive);
|
|
else if (var && var->isPointer() && !Token::Match(tok->tokAt(3), "[.[(]"))
|
|
pointerLessThanZeroError(tok, inconclusive);
|
|
} else if (Token::Match(tok, "0 <= %var%") && tok->tokAt(2)->varId() && !Token::Match(tok->tokAt(3), "+|-|*|/") && !Token::Match(tok->previous(), "+|-|<<|>>|~")) {
|
|
const Variable *var = tok->tokAt(2)->variable();
|
|
if (var && var->typeEndToken()->isUnsigned())
|
|
unsignedPositiveError(tok, var->name(), inconclusive);
|
|
else if (var && var->isPointer() && !Token::Match(tok->tokAt(3), "[.[]"))
|
|
pointerPositiveError(tok, inconclusive);
|
|
} else if (Token::Match(tok, "%var% >= 0") && tok->varId() && !Token::Match(tok->previous(), "++|--|)|+|-|*|/|~|<<|>>") && !Token::Match(tok->tokAt(3), "+|-")) {
|
|
const Variable *var = tok->variable();
|
|
if (var && var->typeEndToken()->isUnsigned())
|
|
unsignedPositiveError(tok, var->name(), inconclusive);
|
|
else if (var && var->isPointer() && tok->strAt(-1) != "*")
|
|
pointerPositiveError(tok, inconclusive);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::unsignedLessThanZeroError(const Token *tok, const std::string &varname, bool inconclusive)
|
|
{
|
|
if (inconclusive) {
|
|
reportError(tok, Severity::style, "unsignedLessThanZero",
|
|
"Checking if unsigned variable '" + varname + "' is less than zero. This might be a false warning.\n"
|
|
"Checking if unsigned variable '" + varname + "' is less than zero. An unsigned "
|
|
"variable will never be negative so it is either pointless or an error to check if it is. "
|
|
"It's not known if the used constant is a template parameter or not and therefore "
|
|
"this message might be a false warning.", true);
|
|
} else {
|
|
reportError(tok, Severity::style, "unsignedLessThanZero",
|
|
"Checking if unsigned variable '" + varname + "' is less than zero.\n"
|
|
"The unsigned variable '" + varname + "' will never be negative so it "
|
|
"is either pointless or an error to check if it is.");
|
|
}
|
|
}
|
|
|
|
void CheckOther::pointerLessThanZeroError(const Token *tok, bool inconclusive)
|
|
{
|
|
reportError(tok, Severity::style, "pointerLessThanZero",
|
|
"A pointer can not be negative so it is either pointless or an error to check if it is.", inconclusive);
|
|
}
|
|
|
|
void CheckOther::unsignedPositiveError(const Token *tok, const std::string &varname, bool inconclusive)
|
|
{
|
|
if (inconclusive) {
|
|
reportError(tok, Severity::style, "unsignedPositive",
|
|
"Unsigned variable '" + varname + "' can't be negative so it is unnecessary to test it.\n"
|
|
"The unsigned variable '" + varname + "' can't be negative so it is unnecessary to test it. "
|
|
"It's not known if the used constant is a "
|
|
"template parameter or not and therefore this message might be a false warning", true);
|
|
} else {
|
|
reportError(tok, Severity::style, "unsignedPositive",
|
|
"Unsigned variable '" + varname + "' can't be negative so it is unnecessary to test it.");
|
|
}
|
|
}
|
|
|
|
void CheckOther::pointerPositiveError(const Token *tok, bool inconclusive)
|
|
{
|
|
reportError(tok, Severity::style, "pointerPositive",
|
|
"A pointer can not be negative so it is either pointless or an error to check if it is not.", inconclusive);
|
|
}
|
|
|
|
/* check if a constructor in given class scope takes a reference */
|
|
static bool constructorTakesReference(const Scope * const classScope)
|
|
{
|
|
for (std::list<Function>::const_iterator func = classScope->functionList.begin(); func != classScope->functionList.end(); ++func) {
|
|
if (func->isConstructor()) {
|
|
const Function &constructor = *func;
|
|
for (std::size_t argnr = 0U; argnr < constructor.argCount(); argnr++) {
|
|
const Variable * const argVar = constructor.getArgumentVar(argnr);
|
|
if (argVar && argVar->isReference()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// This check rule works for checking the "const A a = getA()" usage when getA() returns "const A &" or "A &".
|
|
// In most scenarios, "const A & a = getA()" will be more efficient.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkRedundantCopy()
|
|
{
|
|
if (!_settings->isEnabled("performance") || _tokenizer->isC() || !_settings->inconclusive)
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
for (std::size_t i = 1; i < symbolDatabase->getVariableListSize(); i++) {
|
|
const Variable* var = symbolDatabase->getVariableFromVarId(i);
|
|
|
|
if (!var || var->isReference() || !var->isConst() || var->isPointer() || (!var->type() && !var->isStlType())) // bailout if var is of standard type, if it is a pointer or non-const
|
|
continue;
|
|
|
|
const Token* startTok = var->nameToken();
|
|
if (startTok->strAt(1) == "=") // %type% %var% = ... ;
|
|
;
|
|
else if (startTok->strAt(1) == "(" && var->isClass() && var->typeScope()) {
|
|
// Object is instantiated. Warn if constructor takes arguments by value.
|
|
if (constructorTakesReference(var->typeScope()))
|
|
continue;
|
|
} else
|
|
continue;
|
|
|
|
const Token* tok = startTok->next()->astOperand2();
|
|
if (!tok)
|
|
continue;
|
|
if (!Token::Match(tok->previous(), "%var% ("))
|
|
continue;
|
|
if (!Token::Match(tok->link(), ") )| ;")) // bailout for usage like "const A a = getA()+3"
|
|
continue;
|
|
|
|
const Function* func = tok->previous()->function();
|
|
if (func && func->tokenDef->strAt(-1) == "&") {
|
|
redundantCopyError(startTok, startTok->str());
|
|
}
|
|
}
|
|
}
|
|
void CheckOther::redundantCopyError(const Token *tok,const std::string& varname)
|
|
{
|
|
reportError(tok, Severity::performance, "redundantCopyLocalConst",
|
|
"Use const reference for '" + varname + "' to avoid unnecessary data copying.\n"
|
|
"The const variable '"+varname+"' is assigned a copy of the data. You can avoid "
|
|
"the unnecessary data copying by converting '" + varname + "' to const reference.",
|
|
true); // since #5618 that check became inconlusive
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Checking for shift by negative values
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CheckOther::checkNegativeBitwiseShift()
|
|
{
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (tok->str() != "<<" && tok->str() != ">>")
|
|
continue;
|
|
|
|
if (!tok->astOperand1() || !tok->astOperand2())
|
|
continue;
|
|
|
|
// don't warn if lhs is a class. this is an overloaded operator then
|
|
if (_tokenizer->isCPP()) {
|
|
const Token *rhs = tok->astOperand1();
|
|
while (Token::Match(rhs, "::|."))
|
|
rhs = rhs->astOperand2();
|
|
if (!rhs)
|
|
continue;
|
|
if (!rhs->isNumber() && !rhs->variable())
|
|
continue;
|
|
if (rhs->variable() &&
|
|
(!rhs->variable()->typeStartToken() || !rhs->variable()->typeStartToken()->isStandardType()))
|
|
continue;
|
|
}
|
|
|
|
// Get negative rhs value. preferably a value which doesn't have 'condition'.
|
|
const ValueFlow::Value *value = tok->astOperand2()->getValueLE(-1LL, _settings);
|
|
if (value)
|
|
negativeBitwiseShiftError(tok);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CheckOther::negativeBitwiseShiftError(const Token *tok)
|
|
{
|
|
reportError(tok, Severity::error, "shiftNegative", "Shifting by a negative value is undefined behaviour");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check for incompletely filled buffers.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkIncompleteArrayFill()
|
|
{
|
|
bool warning = _settings->isEnabled("warning");
|
|
bool portability = _settings->isEnabled("portability");
|
|
if (!_settings->inconclusive || (!portability && !warning))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
|
|
if (Token::Match(tok, "memset|memcpy|memmove ( %var% ,") && Token::Match(tok->linkAt(1)->tokAt(-2), ", %num% )")) {
|
|
const Variable *var = tok->tokAt(2)->variable();
|
|
if (!var || !var->isArray() || var->dimensions().empty() || !var->dimension(0))
|
|
continue;
|
|
|
|
if (MathLib::toLongNumber(tok->linkAt(1)->strAt(-1)) == var->dimension(0)) {
|
|
unsigned int size = _tokenizer->sizeOfType(var->typeStartToken());
|
|
if ((size != 1 && size != 100 && size != 0) || var->isPointer()) {
|
|
if (warning)
|
|
incompleteArrayFillError(tok, var->name(), tok->str(), false);
|
|
} else if (var->typeStartToken()->str() == "bool" && portability) // sizeof(bool) is not 1 on all platforms
|
|
incompleteArrayFillError(tok, var->name(), tok->str(), true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::incompleteArrayFillError(const Token* tok, const std::string& buffer, const std::string& function, bool boolean)
|
|
{
|
|
if (boolean)
|
|
reportError(tok, Severity::portability, "incompleteArrayFill",
|
|
"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 + ")'?", true);
|
|
else
|
|
reportError(tok, Severity::warning, "incompleteArrayFill",
|
|
"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 + ")'?", true);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Detect NULL being passed to variadic function.
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CheckOther::checkVarFuncNullUB()
|
|
{
|
|
if (!_settings->isEnabled("portability"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart; tok != scope->classEnd; 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;
|
|
std::size_t 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() <= argnr) {
|
|
const Token *tok2 = f->argDef;
|
|
tok2 = tok2 ? tok2->link() : nullptr; // goto ')'
|
|
if (Token::simpleMatch(tok2->tokAt(-3), ". . ."))
|
|
varFuncNullUBError(tok);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::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 <stdarg.h>\n"
|
|
"#include <stdio.h>\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; i<sizeof(a); i++)\n"
|
|
" a[i] = -1;\n"
|
|
"}\n"
|
|
"\n"
|
|
"int main() {\n"
|
|
" h();\n"
|
|
" g();\n"
|
|
" return 0;\n"
|
|
"}");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Check for ignored return values.
|
|
//---------------------------------------------------------------------------
|
|
void CheckOther::checkIgnoredReturnValue()
|
|
{
|
|
if (!_settings->isEnabled("warning"))
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
|
const std::size_t functions = symbolDatabase->functionScopes.size();
|
|
for (std::size_t i = 0; i < functions; ++i) {
|
|
const Scope * scope = symbolDatabase->functionScopes[i];
|
|
for (const Token* tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) {
|
|
if (tok->varId() || !Token::Match(tok, "%var% (") || tok->strAt(-1) == "." || tok->next()->astOperand1() != tok)
|
|
continue;
|
|
|
|
if (!tok->scope()->isExecutable())
|
|
tok = tok->scope()->classEnd;
|
|
|
|
if (!tok->next()->astParent() && (!tok->function() || !Token::Match(tok->function()->retDef, "void %var%")) && _settings->library.useretval.find(tok->str()) != _settings->library.useretval.end())
|
|
ignoredReturnValueError(tok, tok->str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckOther::ignoredReturnValueError(const Token* tok, const std::string& function)
|
|
{
|
|
reportError(tok, Severity::warning, "ignoredReturnValue",
|
|
"Return value of function " + function + "() is not used.", false);
|
|
}
|