cppcheck/lib/checktype.cpp

508 lines
21 KiB
C++

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2023 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 "errortypes.h"
#include "mathlib.h"
#include "platform.h"
#include "settings.h"
#include "standards.h"
#include "symboldatabase.h"
#include "token.h"
#include "tokenize.h"
#include "valueflow.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <list>
#include <sstream>
#include <utility>
#include <vector>
//---------------------------------------------------------------------------
// Register this check class (by creating a static instance of it)
namespace {
CheckType instance;
}
//---------------------------------------------------------------------------
// Checking for shift by too many bits
//---------------------------------------------------------------------------
//
// CWE ids used:
static const CWE CWE195(195U); // Signed to Unsigned Conversion Error
static const CWE CWE197(197U); // Numeric Truncation Error
static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior
static const CWE CWE190(190U); // Integer Overflow or Wraparound
void CheckType::checkTooBigBitwiseShift()
{
// unknown sizeof(int) => can't run this checker
if (mSettings->platform.type == Platform::Type::Unspecified)
return;
logChecker("CheckType::checkTooBigBitwiseShift"); // platform
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
// C++ and macro: OUT(x<<y)
if (mTokenizer->isCPP() && Token::Match(tok, "[;{}] %name% (") && Token::simpleMatch(tok->linkAt(2), ") ;") && tok->next()->isUpperCaseName() && !tok->next()->function())
tok = tok->linkAt(2);
if (!tok->astOperand1() || !tok->astOperand2())
continue;
if (!Token::Match(tok, "<<|>>|<<=|>>="))
continue;
// get number of bits of lhs
const ValueType * const lhstype = tok->astOperand1()->valueType();
if (!lhstype || !lhstype->isIntegral() || lhstype->pointer >= 1)
continue;
// C11 Standard, section 6.5.7 Bitwise shift operators, states:
// The integer promotions are performed on each of the operands.
// The type of the result is that of the promoted left operand.
int lhsbits;
if ((lhstype->type == ValueType::Type::CHAR) ||
(lhstype->type == ValueType::Type::SHORT) ||
(lhstype->type == ValueType::Type::WCHAR_T) ||
(lhstype->type == ValueType::Type::BOOL) ||
(lhstype->type == ValueType::Type::INT))
lhsbits = mSettings->platform.int_bit;
else if (lhstype->type == ValueType::Type::LONG)
lhsbits = mSettings->platform.long_bit;
else if (lhstype->type == ValueType::Type::LONGLONG)
lhsbits = mSettings->platform.long_long_bit;
else
continue;
// Get biggest rhs value. preferably a value which doesn't have 'condition'.
const ValueFlow::Value * value = tok->astOperand2()->getValueGE(lhsbits, mSettings);
if (value && mSettings->isEnabled(value, false))
tooBigBitwiseShiftError(tok, lhsbits, *value);
else if (lhstype->sign == ValueType::Sign::SIGNED) {
value = tok->astOperand2()->getValueGE(lhsbits-1, mSettings);
if (value && mSettings->isEnabled(value, false))
tooBigSignedBitwiseShiftError(tok, lhsbits, *value);
}
}
}
void CheckType::tooBigBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits)
{
constexpr char id[] = "shiftTooManyBits";
if (!tok) {
reportError(tok, Severity::error, id, "Shifting 32-bit value by 40 bits is undefined behaviour", CWE758, Certainty::normal);
return;
}
const ErrorPath errorPath = getErrorPath(tok, &rhsbits, "Shift");
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(errorPath, rhsbits.errorSeverity() ? Severity::error : Severity::warning, id, errmsg.str(), CWE758, rhsbits.isInconclusive() ? Certainty::inconclusive : Certainty::normal);
}
void CheckType::tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits)
{
constexpr char id[] = "shiftTooManyBitsSigned";
const bool cpp14 = mSettings->standards.cpp >= Standards::CPP14;
std::string behaviour = "undefined";
if (cpp14)
behaviour = "implementation-defined";
if (!tok) {
reportError(tok, Severity::error, id, "Shifting signed 32-bit value by 31 bits is " + behaviour + " behaviour", CWE758, Certainty::normal);
return;
}
Severity severity = rhsbits.errorSeverity() ? Severity::error : Severity::warning;
if (cpp14)
severity = Severity::portability;
if ((severity == Severity::portability) && !mSettings->severity.isEnabled(Severity::portability))
return;
const ErrorPath errorPath = getErrorPath(tok, &rhsbits, "Shift");
std::ostringstream errmsg;
errmsg << "Shifting signed " << lhsbits << "-bit value by " << rhsbits.intvalue << " bits is " + behaviour + " behaviour";
if (rhsbits.condition)
errmsg << ". See condition at line " << rhsbits.condition->linenr() << ".";
reportError(errorPath, severity, id, errmsg.str(), CWE758, rhsbits.isInconclusive() ? Certainty::inconclusive : Certainty::normal);
}
//---------------------------------------------------------------------------
// Checking for integer overflow
//---------------------------------------------------------------------------
void CheckType::checkIntegerOverflow()
{
// unknown sizeof(int) => can't run this checker
if (mSettings->platform.type == Platform::Type::Unspecified || mSettings->platform.int_bit >= MathLib::bigint_bits)
return;
logChecker("CheckType::checkIntegerOverflow"); // platform
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (!tok->isArithmeticalOp())
continue;
// is result signed integer?
const ValueType *vt = tok->valueType();
if (!vt || !vt->isIntegral() || vt->sign != ValueType::Sign::SIGNED)
continue;
unsigned int bits;
if (vt->type == ValueType::Type::INT)
bits = mSettings->platform.int_bit;
else if (vt->type == ValueType::Type::LONG)
bits = mSettings->platform.long_bit;
else if (vt->type == ValueType::Type::LONGLONG)
bits = mSettings->platform.long_long_bit;
else
continue;
if (bits >= MathLib::bigint_bits)
continue;
// max value according to platform settings.
const MathLib::bigint maxvalue = (((MathLib::biguint)1) << (bits - 1)) - 1;
// is there a overflow result value
const ValueFlow::Value *value = tok->getValueGE(maxvalue + 1, mSettings);
if (!value)
value = tok->getValueLE(-maxvalue - 2, mSettings);
if (!value || !mSettings->isEnabled(value,false))
continue;
// For left shift, it's common practice to shift into the sign bit
if (tok->str() == "<<" && value->intvalue > 0 && value->intvalue < (((MathLib::bigint)1) << bits))
continue;
integerOverflowError(tok, *value);
}
}
void CheckType::integerOverflowError(const Token *tok, const ValueFlow::Value &value)
{
const std::string expr(tok ? tok->expressionString() : "");
std::string msg;
if (value.condition)
msg = ValueFlow::eitherTheConditionIsRedundant(value.condition) +
" or there is signed integer overflow for expression '" + expr + "'.";
else
msg = "Signed integer overflow for expression '" + expr + "'.";
if (value.safe)
msg = "Safe checks: " + msg;
reportError(getErrorPath(tok, &value, "Integer overflow"),
value.errorSeverity() ? Severity::error : Severity::warning,
getMessageId(value, "integerOverflow").c_str(),
msg,
CWE190,
value.isInconclusive() ? Certainty::inconclusive : Certainty::normal);
}
//---------------------------------------------------------------------------
// Checking for sign conversion when operand can be negative
//---------------------------------------------------------------------------
void CheckType::checkSignConversion()
{
if (!mSettings->severity.isEnabled(Severity::warning))
return;
logChecker("CheckType::checkSignConversion"); // warning
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (!tok->isArithmeticalOp() || Token::Match(tok,"+|-"))
continue;
// Is result unsigned?
if (!(tok->valueType() && tok->valueType()->sign == ValueType::Sign::UNSIGNED))
continue;
// Check if an operand can be negative..
const Token * astOperands[] = { tok->astOperand1(), tok->astOperand2() };
for (const Token * tok1 : astOperands) {
if (!tok1)
continue;
const ValueFlow::Value* negativeValue =
ValueFlow::findValue(tok1->values(), mSettings, [&](const ValueFlow::Value& v) {
return !v.isImpossible() && v.isIntValue() && (v.intvalue <= -1 || v.wideintvalue <= -1);
});
if (!negativeValue)
continue;
if (tok1->valueType() && tok1->valueType()->sign != ValueType::Sign::UNSIGNED)
signConversionError(tok1, negativeValue, tok1->isNumber());
}
}
}
void CheckType::signConversionError(const Token *tok, const ValueFlow::Value *negativeValue, const bool constvalue)
{
const std::string expr(tok ? tok->expressionString() : "var");
std::ostringstream msg;
if (tok && tok->isName())
msg << "$symbol:" << expr << "\n";
if (constvalue)
msg << "Expression '" << expr << "' has a negative value. That is converted to an unsigned value and used in an unsigned calculation.";
else
msg << "Expression '" << expr << "' can have a negative value. That is converted to an unsigned value and used in an unsigned calculation.";
if (!negativeValue)
reportError(tok, Severity::warning, "signConversion", msg.str(), CWE195, Certainty::normal);
else {
const ErrorPath &errorPath = getErrorPath(tok,negativeValue,"Negative value is converted to an unsigned value");
reportError(errorPath,
Severity::warning,
Check::getMessageId(*negativeValue, "signConversion").c_str(),
msg.str(),
CWE195,
negativeValue->isInconclusive() ? Certainty::inconclusive : Certainty::normal);
}
}
//---------------------------------------------------------------------------
// Checking for long cast of int result const long x = var1 * var2;
//---------------------------------------------------------------------------
static bool checkTypeCombination(const ValueType& src, const ValueType& tgt, const Settings* settings)
{
static const std::pair<ValueType::Type, ValueType::Type> typeCombinations[] = {
{ ValueType::Type::INT, ValueType::Type::LONG },
{ ValueType::Type::INT, ValueType::Type::LONGLONG },
{ ValueType::Type::LONG, ValueType::Type::LONGLONG },
{ ValueType::Type::FLOAT, ValueType::Type::DOUBLE },
{ ValueType::Type::FLOAT, ValueType::Type::LONGDOUBLE },
{ ValueType::Type::DOUBLE, ValueType::Type::LONGDOUBLE },
};
const std::size_t sizeSrc = ValueFlow::getSizeOf(src, settings);
const std::size_t sizeTgt = ValueFlow::getSizeOf(tgt, settings);
if (!(sizeSrc > 0 && sizeTgt > 0 && sizeSrc < sizeTgt))
return false;
return std::any_of(std::begin(typeCombinations), std::end(typeCombinations), [&](const std::pair<ValueType::Type, ValueType::Type>& p) {
return src.type == p.first && tgt.type == p.second;
});
}
void CheckType::checkLongCast()
{
if (!mSettings->severity.isEnabled(Severity::style))
return;
logChecker("CheckType::checkLongCast"); // style
// Assignments..
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
if (tok->str() != "=" || !Token::Match(tok->astOperand2(), "*|<<") || tok->astOperand2()->isUnaryOp("*"))
continue;
if (tok->astOperand2()->hasKnownIntValue()) {
const ValueFlow::Value &v = tok->astOperand2()->values().front();
if (mSettings->platform.isIntValue(v.intvalue))
continue;
}
const ValueType *lhstype = tok->astOperand1() ? tok->astOperand1()->valueType() : nullptr;
const ValueType *rhstype = tok->astOperand2()->valueType();
if (!lhstype || !rhstype)
continue;
if (!checkTypeCombination(*rhstype, *lhstype, mSettings))
continue;
// assign int result to long/longlong const nonpointer?
if (rhstype->pointer == 0U &&
rhstype->originalTypeName.empty() &&
lhstype->pointer == 0U &&
lhstype->originalTypeName.empty())
longCastAssignError(tok, rhstype, lhstype);
}
// Return..
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
// function must return long data
const Token * def = scope->classDef;
if (!Token::Match(def, "%name% (") || !def->next()->valueType())
continue;
const ValueType* retVt = def->next()->valueType();
// return statements
const Token *ret = nullptr;
for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
if (tok->str() == "return") {
if (Token::Match(tok->astOperand1(), "<<|*")) {
const ValueType *type = tok->astOperand1()->valueType();
if (type && checkTypeCombination(*type, *retVt, mSettings) &&
type->pointer == 0U &&
type->originalTypeName.empty())
ret = tok;
}
// All return statements must have problem otherwise no warning
if (ret != tok) {
ret = nullptr;
break;
}
}
}
if (ret)
longCastReturnError(ret, ret->astOperand1()->valueType(), retVt);
}
}
static void makeBaseTypeString(std::string& typeStr)
{
const auto pos = typeStr.find("signed");
if (pos != std::string::npos)
typeStr.erase(typeStr.begin(), typeStr.begin() + pos + 6 + 1);
}
void CheckType::longCastAssignError(const Token *tok, const ValueType* src, const ValueType* tgt)
{
std::string srcStr = src ? src->str() : "int";
makeBaseTypeString(srcStr);
std::string tgtStr = tgt ? tgt->str() : "long";
makeBaseTypeString(tgtStr);
reportError(tok,
Severity::style,
"truncLongCastAssignment",
srcStr + " result is assigned to " + tgtStr + " variable. If the variable is " + tgtStr + " to avoid loss of information, then you have loss of information.\n" +
srcStr + " result is assigned to " + tgtStr + " variable. If the variable is " + tgtStr + " to avoid loss of information, then there is loss of information. To avoid loss of information you must cast a calculation operand to " + tgtStr + ", for example 'l = a * b;' => 'l = (" + tgtStr + ")a * b;'.", CWE197, Certainty::normal);
}
void CheckType::longCastReturnError(const Token *tok, const ValueType* src, const ValueType* tgt)
{
std::string srcStr = src ? src->str() : "int";
makeBaseTypeString(srcStr);
std::string tgtStr = tgt ? tgt->str() : "long";
makeBaseTypeString(tgtStr);
reportError(tok,
Severity::style,
"truncLongCastReturn",
srcStr +" result is returned as " + tgtStr + " value. If the return value is " + tgtStr + " to avoid loss of information, then you have loss of information.\n" +
srcStr +" result is returned as " + tgtStr + " value. If the return value is " + tgtStr + " to avoid loss of information, then there is loss of information. To avoid loss of information you must cast a calculation operand to long, for example 'return a*b;' => 'return (long)a*b'.", CWE197, Certainty::normal);
}
//---------------------------------------------------------------------------
// Checking for float to integer overflow
//---------------------------------------------------------------------------
void CheckType::checkFloatToIntegerOverflow()
{
logChecker("CheckType::checkFloatToIntegerOverflow");
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
const ValueType *vtint, *vtfloat;
// Explicit cast
if (Token::Match(tok, "( %name%") && tok->astOperand1() && !tok->astOperand2()) {
vtint = tok->valueType();
vtfloat = tok->astOperand1()->valueType();
checkFloatToIntegerOverflow(tok, vtint, vtfloat, tok->astOperand1()->values());
}
// Assignment
else if (tok->str() == "=" && tok->astOperand1() && tok->astOperand2()) {
vtint = tok->astOperand1()->valueType();
vtfloat = tok->astOperand2()->valueType();
checkFloatToIntegerOverflow(tok, vtint, vtfloat, tok->astOperand2()->values());
}
else if (tok->str() == "return" && tok->astOperand1() && tok->astOperand1()->valueType() && tok->astOperand1()->valueType()->isFloat()) {
const Scope *scope = tok->scope();
while (scope && scope->type != Scope::ScopeType::eLambda && scope->type != Scope::ScopeType::eFunction)
scope = scope->nestedIn;
if (scope && scope->type == Scope::ScopeType::eFunction && scope->function && scope->function->retDef) {
const ValueType &valueType = ValueType::parseDecl(scope->function->retDef, *mSettings);
vtfloat = tok->astOperand1()->valueType();
checkFloatToIntegerOverflow(tok, &valueType, vtfloat, tok->astOperand1()->values());
}
}
}
}
void CheckType::checkFloatToIntegerOverflow(const Token *tok, const ValueType *vtint, const ValueType *vtfloat, const std::list<ValueFlow::Value> &floatValues)
{
// Conversion of float to integer?
if (!vtint || !vtint->isIntegral())
return;
if (!vtfloat || !vtfloat->isFloat())
return;
for (const ValueFlow::Value &f : floatValues) {
if (f.valueType != ValueFlow::Value::ValueType::FLOAT)
continue;
if (!mSettings->isEnabled(&f, false))
continue;
if (f.floatValue >= std::exp2(mSettings->platform.long_long_bit))
floatToIntegerOverflowError(tok, f);
else if ((-f.floatValue) > std::exp2(mSettings->platform.long_long_bit - 1))
floatToIntegerOverflowError(tok, f);
else if (mSettings->platform.type != Platform::Type::Unspecified) {
int bits = 0;
if (vtint->type == ValueType::Type::CHAR)
bits = mSettings->platform.char_bit;
else if (vtint->type == ValueType::Type::SHORT)
bits = mSettings->platform.short_bit;
else if (vtint->type == ValueType::Type::INT)
bits = mSettings->platform.int_bit;
else if (vtint->type == ValueType::Type::LONG)
bits = mSettings->platform.long_bit;
else if (vtint->type == ValueType::Type::LONGLONG)
bits = mSettings->platform.long_long_bit;
else
continue;
if (bits < MathLib::bigint_bits && f.floatValue >= (((MathLib::biguint)1) << bits))
floatToIntegerOverflowError(tok, f);
}
}
}
void CheckType::floatToIntegerOverflowError(const Token *tok, const ValueFlow::Value &value)
{
std::ostringstream errmsg;
errmsg << "Undefined behaviour: float (" << value.floatValue << ") to integer conversion overflow.";
reportError(getErrorPath(tok, &value, "float to integer conversion"),
value.errorSeverity() ? Severity::error : Severity::warning,
"floatConversionOverflow",
errmsg.str(), CWE190, value.isInconclusive() ? Certainty::inconclusive : Certainty::normal);
}