cppcheck/lib/checktype.cpp

379 lines
14 KiB
C++

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2015 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 "checktype.h"
#include "mathlib.h"
#include "symboldatabase.h"
#include <stack>
//---------------------------------------------------------------------------
// Register this check class (by creating a static instance of it)
namespace {
CheckType instance;
}
static bool astIsIntResult(const Token *tok)
{
if (!tok)
return false;
if (tok->astOperand1())
return astIsIntResult(tok->astOperand1()) && astIsIntResult(tok->astOperand2());
// todo: handle numbers
if (!tok->variable())
return false;
const Token *type = tok->variable()->typeStartToken();
return Token::Match(type, "char|short|int") && !type->isLong();
}
static bool astGetSizeSign(const Settings *settings, const Token *tok, unsigned int *size, char *sign)
{
if (!tok)
return false;
if (tok->isArithmeticalOp()) {
if (!astGetSizeSign(settings, tok->astOperand1(), size, sign))
return false;
return !tok->astOperand2() || astGetSizeSign(settings, tok->astOperand2(), size, sign);
}
if (tok->isNumber() && MathLib::isInt(tok->str())) {
if (tok->str().find("L") != std::string::npos)
return false;
MathLib::bigint value = MathLib::toLongNumber(tok->str());
unsigned int sz;
if (value >= -(1<<7) && value <= (1<<7)-1)
sz = 8;
else if (value >= -(1<<15) && value <= (1<<15)-1)
sz = 16;
else if (value >= -(1LL<<31) && value <= (1LL<<31)-1)
sz = 32;
else
return false;
if (sz < 8 * settings->sizeof_int)
sz = 8 * settings->sizeof_int;
if (*size < sz)
*size = sz;
if (tok->str().find('U') != std::string::npos)
*sign = 'u';
if (*sign != 'u')
*sign = 's';
return true;
}
if (tok->isName()) {
const Variable *var = tok->variable();
if (!var)
return false;
unsigned int sz = 0;
for (const Token *type = var->typeStartToken(); type; type = type->next()) {
if (type->str() == "*")
return false; // <- FIXME: handle pointers
if (Token::Match(type, "char|short|int")) {
sz = 8 * settings->sizeof_int;
if (type->isUnsigned())
*sign = 'u';
else if (*sign != 'u')
*sign = 's';
} else if (Token::Match(type, "float|double|long")) {
return false;
} else {
// TODO: try to lookup type info in library
}
if (type == var->typeEndToken())
break;
}
if (sz == 0)
return false;
if (*size < sz)
*size = sz;
return true;
}
return false;
}
//---------------------------------------------------------------------------
// Checking for shift by too many bits
//---------------------------------------------------------------------------
void CheckType::checkTooBigBitwiseShift()
{
const bool printWarnings = _settings->isEnabled("warning");
const bool printInconclusive = _settings->inconclusive;
// unknown sizeof(int) => can't run this checker
if (_settings->platformType == Settings::Unspecified)
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->str() != "<<" && tok->str() != ">>")
continue;
if (!tok->astOperand1() || !tok->astOperand2())
continue;
// get number of bits of lhs
const Variable *var = tok->astOperand1()->variable();
if (!var)
continue;
int lhsbits = 0;
for (const Token *type = var->typeStartToken(); type; type = type->next()) {
if (Token::Match(type,"char|short|int") && !type->isLong()) {
lhsbits = _settings->sizeof_int * 8;
break;
}
if (type == var->typeEndToken() || type->str() == "<")
break;
}
if (lhsbits == 0)
continue;
// Get biggest rhs value. preferably a value which doesn't have 'condition'.
const ValueFlow::Value *value = tok->astOperand2()->getValueGE(lhsbits, _settings);
if (!value)
continue;
if (value->condition && !printWarnings)
continue;
if (value->inconclusive && !printInconclusive)
continue;
tooBigBitwiseShiftError(tok, lhsbits, *value);
}
}
}
void CheckType::tooBigBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits)
{
std::list<const Token*> callstack;
callstack.push_back(tok);
if (rhsbits.condition)
callstack.push_back(rhsbits.condition);
std::ostringstream errmsg;
errmsg << "Shifting " << lhsbits << "-bit value by " << rhsbits.intvalue << " bits is undefined behaviour";
if (rhsbits.condition)
errmsg << ". See condition at line " << rhsbits.condition->linenr() << ".";
reportError(callstack, rhsbits.condition ? Severity::warning : Severity::error, "shiftTooManyBits", errmsg.str(), 0U, rhsbits.inconclusive);
}
//---------------------------------------------------------------------------
// Checking for integer overflow
//---------------------------------------------------------------------------
void CheckType::checkIntegerOverflow()
{
// unknown sizeof(int) => can't run this checker
if (_settings->platformType == Settings::Unspecified)
return;
// max int value according to platform settings.
const MathLib::bigint maxint = (1LL << (8 * _settings->sizeof_int - 1)) - 1;
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->isArithmeticalOp())
continue;
// is there a overflow result value
const ValueFlow::Value *value = tok->getValueGE(maxint + 1, _settings);
if (!value)
value = tok->getValueLE(-maxint - 2, _settings);
if (!value)
continue;
// get size and sign of result..
unsigned int size = 0;
char sign = 0;
if (!astGetSizeSign(_settings, tok, &size, &sign))
continue;
if (sign != 's') // only signed integer overflow is UB
continue;
integerOverflowError(tok, *value);
}
}
}
void CheckType::integerOverflowError(const Token *tok, const ValueFlow::Value &value)
{
const std::string expr(tok ? tok->expressionString() : "");
const std::string cond(value.condition ?
". See condition at line " + MathLib::toString(value.condition->linenr()) + "." :
"");
reportError(tok,
value.condition ? Severity::warning : Severity::error,
"integerOverflow",
"Signed integer overflow for expression '"+expr+"'"+cond,
0U,
value.inconclusive);
}
//---------------------------------------------------------------------------
// Checking for sign conversion when operand can be negative
//---------------------------------------------------------------------------
void CheckType::checkSignConversion()
{
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->isArithmeticalOp() || Token::Match(tok,"+|-"))
continue;
unsigned int size = 0;
char sign = 0;
if (!astGetSizeSign(_settings, tok, &size, &sign))
continue;
if (sign != 'u')
continue;
// Check if there are signed operands that can be negative..
std::stack<const Token *> tokens;
tokens.push(tok->astOperand1());
tokens.push(tok->astOperand2());
while (!tokens.empty()) {
const Token *tok1 = tokens.top();
tokens.pop();
if (!tok1)
continue;
if (tok1->str() == "(")
continue; // Todo: properly handle casts, function calls, etc
const Variable *var = tok1->variable();
if (var && tok1->getValueLE(-1,_settings)) {
bool signedvar = true; // assume that variable is signed since it can have a negative value
for (const Token *type = var->typeStartToken();; type = type->next()) {
if (type->isUnsigned()) {
signedvar = false;
break;
}
if (type->isSigned())
break;
if (type->isName() && !Token::Match(type, "char|short|int|long|const")) {
signedvar = false;
break;
}
if (type == var->typeEndToken())
break;
}
if (signedvar) {
signConversionError(tok1);
break;
}
}
}
}
}
}
void CheckType::signConversionError(const Token *tok)
{
const std::string varname(tok ? tok->str() : "var");
reportError(tok,
Severity::warning,
"signConversion",
"Suspicious code: sign conversion of " + varname + " in calculation, even though " + varname + " can have a negative value");
}
//---------------------------------------------------------------------------
// Checking for long cast of int result const long x = var1 * var2;
//---------------------------------------------------------------------------
void CheckType::checkLongCast()
{
if (!_settings->isEnabled("style"))
return;
// Assignments..
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) {
if (!Token::Match(tok, "%var% ="))
continue;
if (!tok->variable() || !tok->variable()->isConst() || tok->variable()->typeStartToken()->str() != "long")
continue;
if (!tok->variable()->typeStartToken()->originalName().empty())
continue;
if (Token::Match(tok->next()->astOperand2(), "*|<<") && astIsIntResult(tok->next()->astOperand2()))
longCastAssignError(tok);
}
// 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];
// function must return long data
const Token * def = scope->classDef;
bool islong = false;
while (Token::Match(def, "%type%|::")) {
if (def->str() == "long" && def->originalName().empty()) {
islong = true;
break;
}
def = def->previous();
}
if (!islong)
continue;
// find return statement
// todo.. this is slow, we should only check last statement in each child scope
const Token *ret = nullptr;
for (const Token *tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) {
if (tok->str() == "return") {
if (!ret)
ret = tok;
else {
ret = nullptr;
break;
}
}
}
if (ret && Token::Match(ret->astOperand1(), "*|<<") && astIsIntResult(ret->astOperand1()))
longCastReturnError(ret);
}
}
void CheckType::longCastAssignError(const Token *tok)
{
reportError(tok,
Severity::style,
"truncLongCastAssignment",
"possible loss of information, int result is assigned to long variable");
}
void CheckType::longCastReturnError(const Token *tok)
{
reportError(tok,
Severity::style,
"truncLongCastReturn",
"possible loss of information, int result is returned as long value");
}