707 lines
29 KiB
C++
707 lines
29 KiB
C++
/*
|
|
* Cppcheck - A tool for static C/C++ code analysis
|
|
* Copyright (C) 2007-2021 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 "bughuntingchecks.h"
|
|
#include "astutils.h"
|
|
#include "errorlogger.h"
|
|
#include "settings.h"
|
|
#include "symboldatabase.h"
|
|
#include "token.h"
|
|
#include <cstring>
|
|
|
|
static const CWE CWE_BUFFER_UNDERRUN(786U); // Access of Memory Location Before Start of Buffer
|
|
static const CWE CWE_BUFFER_OVERRUN(788U); // Access of Memory Location After End of Buffer
|
|
|
|
|
|
static float getKnownFloatValue(const Token *tok, float def)
|
|
{
|
|
for (const auto &value: tok->values()) {
|
|
if (value.isKnown() && value.valueType == ValueFlow::Value::ValueType::FLOAT)
|
|
return value.floatValue;
|
|
}
|
|
return def;
|
|
}
|
|
|
|
static bool isLessThan(ExprEngine::DataBase *dataBase, ExprEngine::ValuePtr lhs, ExprEngine::ValuePtr rhs)
|
|
{
|
|
return ExprEngine::BinOpResult("<", lhs, rhs).isTrue(dataBase);
|
|
}
|
|
|
|
static void arrayIndex(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
|
|
{
|
|
if (!Token::simpleMatch(tok->astParent(), "["))
|
|
return;
|
|
int nr = 0;
|
|
const Token *buf = tok->astParent()->astOperand1();
|
|
while (Token::simpleMatch(buf, "[")) {
|
|
++nr;
|
|
buf = buf->astOperand1();
|
|
}
|
|
if (!buf || !buf->variable() || !buf->variable()->isArray() || buf == buf->variable()->nameToken())
|
|
// TODO
|
|
return;
|
|
const Token *index = tok->astParent()->astOperand2();
|
|
if (tok != index)
|
|
// TODO
|
|
return;
|
|
if (buf->variable()->dimensions().size() > nr && buf->variable()->dimensions()[nr].known) {
|
|
const MathLib::bigint bufSize = buf->variable()->dimensions()[nr].num;
|
|
if (value.isGreaterThan(dataBase, bufSize - 1)) {
|
|
const bool bailout = (value.type == ExprEngine::ValueType::BailoutValue);
|
|
dataBase->reportError(tok,
|
|
Severity::SeverityType::error,
|
|
"bughuntingArrayIndexOutOfBounds",
|
|
"Array index out of bounds, cannot determine that " + index->expressionString() + " is less than " + std::to_string(bufSize),
|
|
CWE_BUFFER_OVERRUN,
|
|
false,
|
|
bailout);
|
|
}
|
|
}
|
|
bool isUnsigned = tok->valueType() && tok->valueType()->sign == ::ValueType::Sign::UNSIGNED;
|
|
if (!isUnsigned && value.isLessThan(dataBase, 0)) {
|
|
const bool bailout = (value.type == ExprEngine::ValueType::BailoutValue);
|
|
dataBase->reportError(tok,
|
|
Severity::SeverityType::error,
|
|
"bughuntingArrayIndexNegative",
|
|
"Array index out of bounds, cannot determine that " + index->expressionString() + " is not negative",
|
|
CWE_BUFFER_UNDERRUN,
|
|
false,
|
|
bailout);
|
|
}
|
|
}
|
|
|
|
static void bufferOverflow(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
|
|
{
|
|
if (value.type != ExprEngine::ValueType::FunctionCallArgumentValues)
|
|
return;
|
|
if (!Token::simpleMatch(tok, "(") || !Token::Match(tok->previous(), "%name% ("))
|
|
return;
|
|
|
|
const Library::Function *func = dataBase->settings->library.getFunction(tok->previous());
|
|
if (!func)
|
|
return;
|
|
|
|
const ExprEngine::FunctionCallArgumentValues *functionCallArguments = dynamic_cast<const ExprEngine::FunctionCallArgumentValues *>(&value);
|
|
if (!functionCallArguments)
|
|
return;
|
|
|
|
const std::vector<const Token *> arguments = getArguments(tok);
|
|
if (functionCallArguments->argValues.size() != arguments.size())
|
|
// TODO investigate what to do
|
|
return;
|
|
|
|
int overflowArgument = 0;
|
|
bool bailout = false;
|
|
|
|
for (auto argNrChecks: func->argumentChecks) {
|
|
const int argnr = argNrChecks.first;
|
|
const Library::ArgumentChecks &checks = argNrChecks.second;
|
|
if (argnr <= 0 || argnr > arguments.size() || checks.minsizes.empty())
|
|
continue;
|
|
|
|
ExprEngine::ValuePtr argValue = functionCallArguments->argValues[argnr - 1];
|
|
if (!argValue || argValue->type == ExprEngine::ValueType::BailoutValue) {
|
|
overflowArgument = argnr;
|
|
bailout = true;
|
|
break;
|
|
}
|
|
|
|
std::shared_ptr<ExprEngine::ArrayValue> arrayValue = std::dynamic_pointer_cast<ExprEngine::ArrayValue>(argValue);
|
|
if (!arrayValue || arrayValue->size.size() != 1) {
|
|
// TODO: implement this properly.
|
|
overflowArgument = argnr;
|
|
bailout = true;
|
|
break;
|
|
}
|
|
|
|
for (const Library::ArgumentChecks::MinSize &minsize: checks.minsizes) {
|
|
if (minsize.type == Library::ArgumentChecks::MinSize::Type::ARGVALUE && minsize.arg > 0 && minsize.arg <= arguments.size()) {
|
|
ExprEngine::ValuePtr otherValue = functionCallArguments->argValues[minsize.arg - 1];
|
|
if (!otherValue || otherValue->type == ExprEngine::ValueType::BailoutValue) {
|
|
overflowArgument = argnr;
|
|
bailout = true;
|
|
break;
|
|
}
|
|
if (isLessThan(dataBase, arrayValue->size[0], otherValue)) {
|
|
overflowArgument = argnr;
|
|
break;
|
|
}
|
|
} else if (minsize.type == Library::ArgumentChecks::MinSize::Type::STRLEN && minsize.arg > 0 && minsize.arg <= arguments.size()) {
|
|
if (func->formatstr) {
|
|
// TODO: implement this properly. check if minsize refers to a format string and check max length of that..
|
|
overflowArgument = argnr;
|
|
bailout = true;
|
|
break;
|
|
}
|
|
if (Token::Match(arguments[minsize.arg - 1], "%str%")) {
|
|
const Token * const str = arguments[minsize.arg - 1];
|
|
if (arrayValue->size[0]->isLessThan(dataBase, Token::getStrLength(str))) {
|
|
overflowArgument = argnr;
|
|
break;
|
|
}
|
|
} else {
|
|
ExprEngine::ValuePtr otherValue = functionCallArguments->argValues[minsize.arg - 1];
|
|
if (!otherValue || otherValue->type == ExprEngine::ValueType::BailoutValue) {
|
|
overflowArgument = argnr;
|
|
bailout = true;
|
|
break;
|
|
}
|
|
std::shared_ptr<ExprEngine::ArrayValue> arrayValue2 = std::dynamic_pointer_cast<ExprEngine::ArrayValue>(otherValue);
|
|
if (!arrayValue2 || arrayValue2->size.size() != 1) {
|
|
overflowArgument = argnr;
|
|
bailout = true;
|
|
break;
|
|
}
|
|
if (isLessThan(dataBase, arrayValue->size[0], arrayValue2->size[0])) {
|
|
overflowArgument = argnr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (overflowArgument > 0)
|
|
break;
|
|
}
|
|
|
|
if (overflowArgument == 0)
|
|
return;
|
|
|
|
dataBase->reportError(tok,
|
|
Severity::SeverityType::error,
|
|
"bughuntingBufferOverflow",
|
|
"Buffer read/write, when calling '" + tok->previous()->str() + "' it cannot be determined that " + std::to_string(overflowArgument) + getOrdinalText(overflowArgument) + " argument is not overflowed",
|
|
CWE(120),
|
|
false,
|
|
bailout);
|
|
}
|
|
|
|
static void divByZero(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
|
|
{
|
|
if (!tok->astParent() || !std::strchr("/%", tok->astParent()->str()[0]))
|
|
return;
|
|
if (tok->hasKnownIntValue() && tok->getKnownIntValue() != 0)
|
|
return;
|
|
if (tok->isImpossibleIntValue(0))
|
|
return;
|
|
if (value.isUninit() && value.type != ExprEngine::ValueType::BailoutValue)
|
|
return;
|
|
float f = getKnownFloatValue(tok, 0.0f);
|
|
if (f > 0.0f || f < 0.0f)
|
|
return;
|
|
if (value.type == ExprEngine::ValueType::BailoutValue) {
|
|
if (Token::simpleMatch(tok->previous(), "sizeof ("))
|
|
return;
|
|
}
|
|
if (tok->astParent()->astOperand2() == tok && value.isEqual(dataBase, 0)) {
|
|
const char * const id = (tok->valueType() && tok->valueType()->isFloat()) ? "bughuntingDivByZeroFloat" : "bughuntingDivByZero";
|
|
const bool bailout = (value.type == ExprEngine::ValueType::BailoutValue);
|
|
dataBase->reportError(dataBase->settings->clang ? tok : tok->astParent(),
|
|
Severity::SeverityType::error,
|
|
id,
|
|
"There is division, cannot determine that there can't be a division by zero.",
|
|
CWE(369),
|
|
false,
|
|
bailout);
|
|
}
|
|
}
|
|
|
|
#ifdef BUG_HUNTING_INTEGEROVERFLOW
|
|
static void integerOverflow(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
|
|
{
|
|
if (!tok->isArithmeticalOp() || !tok->valueType() || !tok->valueType()->isIntegral() || tok->valueType()->pointer > 0)
|
|
return;
|
|
|
|
const ExprEngine::BinOpResult *b = dynamic_cast<const ExprEngine::BinOpResult *>(&value);
|
|
if (!b)
|
|
return;
|
|
|
|
int bits = getIntBitsFromValueType(tok->valueType(), *dataBase->settings);
|
|
if (bits == 0 || bits >= 60)
|
|
return;
|
|
|
|
std::string errorMessage;
|
|
if (tok->valueType()->sign == ::ValueType::Sign::SIGNED) {
|
|
MathLib::bigint v = 1LL << (bits - 1);
|
|
if (b->isGreaterThan(dataBase, v-1))
|
|
errorMessage = "greater than " + std::to_string(v - 1);
|
|
if (b->isLessThan(dataBase, -v)) {
|
|
if (!errorMessage.empty())
|
|
errorMessage += " or ";
|
|
errorMessage += "less than " + std::to_string(-v);
|
|
}
|
|
} else {
|
|
MathLib::bigint maxValue = (1LL << bits) - 1;
|
|
if (b->isGreaterThan(dataBase, maxValue))
|
|
errorMessage = "greater than " + std::to_string(maxValue);
|
|
if (b->isLessThan(dataBase, 0)) {
|
|
if (!errorMessage.empty())
|
|
errorMessage += " or ";
|
|
errorMessage += "less than 0";
|
|
}
|
|
}
|
|
|
|
if (errorMessage.empty())
|
|
return;
|
|
|
|
|
|
errorMessage = "There is integer arithmetic, cannot determine that there can't be overflow (if result is " + errorMessage + ").";
|
|
|
|
if (tok->valueType()->sign == ::ValueType::Sign::UNSIGNED)
|
|
errorMessage += " Note that unsigned integer overflow is defined and will wrap around.";
|
|
|
|
dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingIntegerOverflow", errorMessage, false, value.type == ExprEngine::ValueType::BailoutValue);
|
|
}
|
|
#endif
|
|
|
|
/** check if variable is unconditionally assigned */
|
|
static bool isVariableAssigned(const Variable *var, const Token *tok, const Token *scopeStart=nullptr)
|
|
{
|
|
const Token * const start = scopeStart && precedes(var->nameToken(), scopeStart) ? scopeStart : var->nameToken();
|
|
|
|
for (const Token *prev = tok->previous(); prev; prev = prev->previous()) {
|
|
if (!precedes(start, prev))
|
|
break;
|
|
|
|
if (prev->str() == "}") {
|
|
if (Token::simpleMatch(prev->link()->tokAt(-2), "} else {")) {
|
|
const Token *elseEnd = prev;
|
|
const Token *elseStart = prev->link();
|
|
const Token *ifEnd = elseStart->tokAt(-2);
|
|
const Token *ifStart = ifEnd->link();
|
|
if (isVariableAssigned(var, ifEnd, ifStart) && isVariableAssigned(var, elseEnd, elseStart)) {
|
|
return true;
|
|
}
|
|
}
|
|
prev = prev->link();
|
|
}
|
|
if (scopeStart && Token::Match(prev, "return|throw|continue|break"))
|
|
return true;
|
|
if (Token::Match(prev, "%varid% =", var->declarationId())) {
|
|
bool usedInRhs = false;
|
|
visitAstNodes(prev->next()->astOperand2(), [&usedInRhs, var](const Token *tok) {
|
|
if (tok->varId() == var->declarationId()) {
|
|
usedInRhs = true;
|
|
return ChildrenToVisit::done;
|
|
}
|
|
return ChildrenToVisit::op1_and_op2;
|
|
});
|
|
if (!usedInRhs)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void uninit(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
|
|
{
|
|
if (!tok->astParent())
|
|
return;
|
|
|
|
std::string uninitStructMember;
|
|
if (const auto* structValue = dynamic_cast<const ExprEngine::StructValue*>(&value)) {
|
|
uninitStructMember = structValue->getUninitStructMember();
|
|
|
|
// uninitialized struct member => is there data copy of struct..
|
|
if (!uninitStructMember.empty()) {
|
|
if (!Token::Match(tok->astParent(), "[=,(]"))
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool uninitData = false;
|
|
if (!value.isUninit() && uninitStructMember.empty()) {
|
|
if (Token::Match(tok->astParent(), "[(,]")) {
|
|
if (const auto* arrayValue = dynamic_cast<const ExprEngine::ArrayValue*>(&value)) {
|
|
uninitData = arrayValue->data.size() >= 1 && arrayValue->data[0].value->isUninit();
|
|
}
|
|
}
|
|
|
|
if (!uninitData)
|
|
return;
|
|
}
|
|
|
|
// container is not uninitialized
|
|
if (tok->valueType() && tok->valueType()->pointer==0 && tok->valueType()->container)
|
|
return;
|
|
|
|
// container element is not uninitialized
|
|
if (tok->str() == "[" &&
|
|
tok->astOperand1() &&
|
|
tok->astOperand1()->valueType() &&
|
|
tok->astOperand1()->valueType()->pointer==0 &&
|
|
tok->astOperand1()->valueType()->container) {
|
|
if (tok->astOperand1()->valueType()->container->stdStringLike)
|
|
return;
|
|
bool pointerType = false;
|
|
for (const Token *typeTok = tok->astOperand1()->valueType()->containerTypeToken; Token::Match(typeTok, "%name%|*|::|<"); typeTok = typeTok->next()) {
|
|
if (typeTok->str() == "<" && typeTok->link())
|
|
typeTok = typeTok->link();
|
|
if (typeTok->str() == "*")
|
|
pointerType = true;
|
|
}
|
|
if (!pointerType)
|
|
return;
|
|
}
|
|
|
|
// variable that is not uninitialized..
|
|
if (tok->variable() && !tok->variable()->isPointer() && !tok->variable()->isReference()) {
|
|
// smart pointer is not uninitialized
|
|
if (tok->variable()->isSmartPointer())
|
|
return;
|
|
|
|
// struct
|
|
if (tok->variable()->type() && tok->variable()->type()->needInitialization == Type::NeedInitialization::False)
|
|
return;
|
|
|
|
// template variable is not uninitialized
|
|
if (Token::findmatch(tok->variable()->typeStartToken(), "%name% <", tok->variable()->typeEndToken()))
|
|
return;
|
|
}
|
|
|
|
// lhs in assignment
|
|
if (tok->astParent()->str() == "=" && tok == tok->astParent()->astOperand1())
|
|
return;
|
|
|
|
// Avoid FP when there is bailout..
|
|
if (value.type == ExprEngine::ValueType::BailoutValue) {
|
|
if (tok->hasKnownValue())
|
|
return;
|
|
if (!tok->variable())
|
|
// FIXME
|
|
return;
|
|
|
|
// lhs for scope operator
|
|
if (Token::Match(tok, "%name% ::"))
|
|
return;
|
|
if (tok->astParent()->str() == "::" && tok == tok->astParent()->astOperand1())
|
|
return;
|
|
|
|
// Object allocated on the stack
|
|
if (Token::Match(tok, "%var% .") && tok->next()->originalName() != "->")
|
|
return;
|
|
|
|
// Assume that stream object is initialized
|
|
if (Token::Match(tok->previous(), "[;{}] %var% <<|>>") && !tok->next()->astParent())
|
|
return;
|
|
|
|
// Containers are not uninitialized
|
|
std::vector<const Token *> tokens{tok, tok->astOperand1(), tok->astOperand2()};
|
|
if (Token::Match(tok->previous(), ". %name%"))
|
|
tokens.push_back(tok->previous()->astOperand1());
|
|
for (const Token *t: tokens) {
|
|
if (t && t->valueType() && t->valueType()->pointer == 0 && t->valueType()->container)
|
|
return;
|
|
}
|
|
|
|
const Variable *var = tok->variable();
|
|
if (var && var->nameToken() == tok)
|
|
return;
|
|
if (var && !var->isLocal())
|
|
return; // FIXME
|
|
if (var && !var->isPointer()) {
|
|
if (!var->isLocal() || var->isStatic())
|
|
return;
|
|
}
|
|
if (var && (Token::Match(var->nameToken(), "%name% [=:({)]") || var->isInit()))
|
|
return;
|
|
if (var && var->nameToken() == tok)
|
|
return;
|
|
|
|
// Are there unconditional assignment?
|
|
if (var && Token::Match(var->nameToken(), "%varid% ;| %varid%| =", tok->varId()))
|
|
return;
|
|
|
|
// Arrays are allocated on the stack
|
|
if (var && Token::Match(tok, "%var% [") && var->isArray())
|
|
return;
|
|
|
|
if (tok->variable() && isVariableAssigned(tok->variable(), tok))
|
|
return;
|
|
}
|
|
|
|
// Uninitialized function argument
|
|
bool inconclusive = false;
|
|
if (Token::Match(tok->astParent(), "[,(]")) {
|
|
const Token *parent = tok->astParent();
|
|
int count = 0;
|
|
if (Token::simpleMatch(parent, ",")) {
|
|
if (tok == parent->astOperand2())
|
|
count = 1;
|
|
parent = parent->astParent();
|
|
while (Token::simpleMatch(parent, ",")) {
|
|
count++;
|
|
parent = parent->astParent();
|
|
}
|
|
}
|
|
if (Token::simpleMatch(parent, "(") && parent->astOperand1() != tok) {
|
|
if (parent->astOperand1()->function()) {
|
|
const Variable *argvar = parent->astOperand1()->function()->getArgumentVar(count);
|
|
if (argvar && argvar->isReference() && !argvar->isConst())
|
|
return;
|
|
if (uninitData && argvar && !argvar->isConst()) {
|
|
if (parent->astOperand1()->function()->hasBody())
|
|
return;
|
|
inconclusive = true;
|
|
}
|
|
if (!uninitStructMember.empty() && dataBase->isC() && argvar && !argvar->isConst()) {
|
|
if (parent->astOperand1()->function()->hasBody())
|
|
return;
|
|
inconclusive = true;
|
|
}
|
|
} else if (uninitData) {
|
|
if (dataBase->settings->library.getFunction(parent->astOperand1()))
|
|
return;
|
|
if (parent->astOperand1()->isKeyword())
|
|
return;
|
|
}
|
|
} else if (uninitData)
|
|
return;
|
|
}
|
|
|
|
if (inconclusive && !dataBase->settings->certainty.isEnabled(Certainty::inconclusive))
|
|
return;
|
|
|
|
// Avoid FP for array declaration
|
|
const Token *parent = tok->astParent();
|
|
while (parent && parent->str() == "[")
|
|
parent = parent->astParent();
|
|
if (!parent)
|
|
return;
|
|
|
|
const std::string inconclusiveMessage(inconclusive ? ". It is inconclusive if there would be a problem in the function call." : "");
|
|
|
|
if (!uninitStructMember.empty()) {
|
|
const std::string symbol = tok->expressionString() + "." + uninitStructMember;
|
|
dataBase->reportError(tok,
|
|
Severity::SeverityType::error,
|
|
"bughuntingUninitStructMember",
|
|
"$symbol:" + symbol + "\nCannot determine that '$symbol' is initialized" + inconclusiveMessage,
|
|
CWE_USE_OF_UNINITIALIZED_VARIABLE,
|
|
inconclusive,
|
|
value.type == ExprEngine::ValueType::BailoutValue);
|
|
return;
|
|
}
|
|
|
|
std::string uninitexpr = tok->expressionString();
|
|
if (uninitData)
|
|
uninitexpr += "[0]";
|
|
|
|
const std::string symbol = (tok->varId() > 0) ? ("$symbol:" + tok->str() + "\n") : std::string();
|
|
|
|
std::string constMessage;
|
|
std::string errorId = "bughuntingUninit";
|
|
|
|
{
|
|
const Token *vartok = tok;
|
|
while (Token::Match(vartok, ".|["))
|
|
vartok = vartok->astOperand1();
|
|
const Variable *var = vartok ? vartok->variable() : nullptr;
|
|
if (var && var->isArgument()) {
|
|
errorId += "NonConstArg";
|
|
constMessage = " (you can use 'const' to say data must be initialized)";
|
|
}
|
|
}
|
|
|
|
|
|
dataBase->reportError(tok,
|
|
Severity::SeverityType::error,
|
|
errorId.c_str(),
|
|
symbol + "Cannot determine that '" + uninitexpr + "' is initialized" + constMessage + inconclusiveMessage,
|
|
CWE_USE_OF_UNINITIALIZED_VARIABLE,
|
|
inconclusive,
|
|
value.type == ExprEngine::ValueType::BailoutValue);
|
|
}
|
|
|
|
static void checkFunctionCall(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
|
|
{
|
|
if (!Token::Match(tok->astParent(), "[(,]"))
|
|
return;
|
|
const Token *parent = tok->astParent();
|
|
while (Token::simpleMatch(parent, ","))
|
|
parent = parent->astParent();
|
|
if (!parent || parent->str() != "(")
|
|
return;
|
|
|
|
int num = 0;
|
|
for (const Token *argTok: getArguments(parent->astOperand1())) {
|
|
--num;
|
|
if (argTok == tok) {
|
|
num = -num;
|
|
break;
|
|
}
|
|
}
|
|
if (num <= 0)
|
|
return;
|
|
|
|
if (parent->astOperand1()->function()) {
|
|
const Variable *arg = parent->astOperand1()->function()->getArgumentVar(num - 1);
|
|
if (arg && arg->nameToken()) {
|
|
std::string bad;
|
|
|
|
MathLib::bigint low;
|
|
if (arg->nameToken()->getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type::LOW, &low)) {
|
|
if (!(tok->hasKnownIntValue() && tok->getKnownIntValue() >= low) && value.isLessThan(dataBase, low))
|
|
bad = "__cppcheck_low__(" + std::to_string(low) + ")";
|
|
}
|
|
|
|
MathLib::bigint high;
|
|
if (arg->nameToken()->getCppcheckAttribute(TokenImpl::CppcheckAttributes::Type::HIGH, &high)) {
|
|
if (!(tok->hasKnownIntValue() && tok->getKnownIntValue() <= high) && value.isGreaterThan(dataBase, high))
|
|
bad = "__cppcheck_high__(" + std::to_string(high) + ")";
|
|
}
|
|
|
|
if (!bad.empty()) {
|
|
dataBase->reportError(tok,
|
|
Severity::SeverityType::error,
|
|
"bughuntingInvalidArgValue",
|
|
"There is function call, cannot determine that " + std::to_string(num) + getOrdinalText(num) + " argument value meets the attribute " + bad,
|
|
CWE(0),
|
|
false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check invalid function argument values..
|
|
for (const Library::InvalidArgValue &invalidArgValue : Library::getInvalidArgValues(dataBase->settings->library.validarg(parent->astOperand1(), num))) {
|
|
bool err = false;
|
|
std::string bad;
|
|
switch (invalidArgValue.type) {
|
|
case Library::InvalidArgValue::Type::eq:
|
|
if (!tok->hasKnownIntValue() || tok->getKnownIntValue() == MathLib::toLongNumber(invalidArgValue.op1))
|
|
err = value.isEqual(dataBase, MathLib::toLongNumber(invalidArgValue.op1));
|
|
bad = "equals " + invalidArgValue.op1;
|
|
break;
|
|
case Library::InvalidArgValue::Type::le:
|
|
if (!tok->hasKnownIntValue() || tok->getKnownIntValue() <= MathLib::toLongNumber(invalidArgValue.op1))
|
|
err = value.isLessThan(dataBase, MathLib::toLongNumber(invalidArgValue.op1) + 1);
|
|
bad = "less equal " + invalidArgValue.op1;
|
|
break;
|
|
case Library::InvalidArgValue::Type::lt:
|
|
if (!tok->hasKnownIntValue() || tok->getKnownIntValue() < MathLib::toLongNumber(invalidArgValue.op1))
|
|
err = value.isLessThan(dataBase, MathLib::toLongNumber(invalidArgValue.op1));
|
|
bad = "less than " + invalidArgValue.op1;
|
|
break;
|
|
case Library::InvalidArgValue::Type::ge:
|
|
if (!tok->hasKnownIntValue() || tok->getKnownIntValue() >= MathLib::toLongNumber(invalidArgValue.op1))
|
|
err = value.isGreaterThan(dataBase, MathLib::toLongNumber(invalidArgValue.op1) - 1);
|
|
bad = "greater equal " + invalidArgValue.op1;
|
|
break;
|
|
case Library::InvalidArgValue::Type::gt:
|
|
if (!tok->hasKnownIntValue() || tok->getKnownIntValue() > MathLib::toLongNumber(invalidArgValue.op1))
|
|
err = value.isGreaterThan(dataBase, MathLib::toLongNumber(invalidArgValue.op1));
|
|
bad = "greater than " + invalidArgValue.op1;
|
|
break;
|
|
case Library::InvalidArgValue::Type::range:
|
|
// TODO
|
|
err = value.isEqual(dataBase, MathLib::toLongNumber(invalidArgValue.op1));
|
|
err |= value.isEqual(dataBase, MathLib::toLongNumber(invalidArgValue.op2));
|
|
bad = "range " + invalidArgValue.op1 + "-" + invalidArgValue.op2;
|
|
break;
|
|
}
|
|
|
|
if (err) {
|
|
dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingInvalidArgValue", "There is function call, cannot determine that " + std::to_string(num) + getOrdinalText(num) + " argument value is valid. Bad value: " + bad, CWE(0), false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Uninitialized function argument..
|
|
if (dataBase->settings->library.isuninitargbad(parent->astOperand1(), num) && dataBase->settings->library.isnullargbad(parent->astOperand1(), num) && value.type == ExprEngine::ValueType::ArrayValue) {
|
|
const ExprEngine::ArrayValue &arrayValue = static_cast<const ExprEngine::ArrayValue &>(value);
|
|
auto index0 = std::make_shared<ExprEngine::IntRange>("0", 0, 0);
|
|
for (const auto &v: arrayValue.read(index0)) {
|
|
if (v.second->isUninit()) {
|
|
dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingUninitArg", "There is function call, cannot determine that " + std::to_string(num) + getOrdinalText(num) + " argument is initialized.", CWE_USE_OF_UNINITIALIZED_VARIABLE, false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void checkAssignment(const Token *tok, const ExprEngine::Value &value, ExprEngine::DataBase *dataBase)
|
|
{
|
|
if (!Token::simpleMatch(tok->astParent(), "="))
|
|
return;
|
|
const Token *lhs = tok->astParent()->astOperand1();
|
|
while (Token::simpleMatch(lhs, "."))
|
|
lhs = lhs->astOperand2();
|
|
if (!lhs || !lhs->variable() || !lhs->variable()->nameToken())
|
|
return;
|
|
|
|
const Token *vartok = lhs->variable()->nameToken();
|
|
|
|
bool executable = false;
|
|
std::string fullName = lhs->variable()->name();
|
|
for (const Scope *s = lhs->variable()->nameToken()->scope(); s->type != Scope::ScopeType::eGlobal; s = s->nestedIn) {
|
|
if (s->isExecutable()) {
|
|
executable = true;
|
|
break;
|
|
}
|
|
fullName = s->className + "::" + fullName;
|
|
}
|
|
|
|
auto getMinMaxValue = [=](TokenImpl::CppcheckAttributes::Type type, MathLib::bigint *val) {
|
|
if (vartok->getCppcheckAttribute(type, val))
|
|
return true;
|
|
if (!executable) {
|
|
const auto it = dataBase->settings->variableContracts.find(fullName);
|
|
if (it != dataBase->settings->variableContracts.end()) {
|
|
const std::string *str;
|
|
if (type == TokenImpl::CppcheckAttributes::Type::LOW)
|
|
str = &it->second.minValue;
|
|
else if (type == TokenImpl::CppcheckAttributes::Type::HIGH)
|
|
str = &it->second.maxValue;
|
|
else
|
|
return false;
|
|
*val = MathLib::toLongNumber(*str);
|
|
return !str->empty();
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
MathLib::bigint low;
|
|
if (getMinMaxValue(TokenImpl::CppcheckAttributes::Type::LOW, &low)) {
|
|
if (value.isLessThan(dataBase, low))
|
|
dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingAssign", "There is assignment, cannot determine that value is greater or equal with " + std::to_string(low), CWE_INCORRECT_CALCULATION, false);
|
|
}
|
|
|
|
MathLib::bigint high;
|
|
if (getMinMaxValue(TokenImpl::CppcheckAttributes::Type::HIGH, &high)) {
|
|
if (value.isGreaterThan(dataBase, high))
|
|
dataBase->reportError(tok, Severity::SeverityType::error, "bughuntingAssign", "There is assignment, cannot determine that value is lower or equal with " + std::to_string(high), CWE_INCORRECT_CALCULATION, false);
|
|
}
|
|
}
|
|
|
|
void addBughuntingChecks(std::vector<ExprEngine::Callback> *callbacks)
|
|
{
|
|
callbacks->push_back(arrayIndex);
|
|
callbacks->push_back(bufferOverflow);
|
|
callbacks->push_back(divByZero);
|
|
callbacks->push_back(checkFunctionCall);
|
|
callbacks->push_back(checkAssignment);
|
|
#ifdef BUG_HUNTING_INTEGEROVERFLOW
|
|
callbacks->push_back(integerOverflow);
|
|
#endif
|
|
callbacks->push_back(uninit);
|
|
|
|
}
|
|
|