Fix issue 9360: False positive: arrayIndexOutOfBounds when function is called with different array sizes (#2541)

This commit is contained in:
Paul Fultz II 2020-02-17 03:31:08 -06:00 committed by GitHub
parent 7044c17599
commit 3b20684aca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 59 deletions

View File

@ -182,7 +182,7 @@ static int getMinFormatStringOutputLength(const std::vector<const Token*> &param
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
static bool getDimensionsEtc(const Token * const arrayToken, const Settings *settings, std::vector<Dimension> * const dimensions, ErrorPath * const errorPath, bool * const mightBeLarger) static bool getDimensionsEtc(const Token * const arrayToken, const Settings *settings, std::vector<Dimension> * const dimensions, ErrorPath * const errorPath, bool * const mightBeLarger, MathLib::bigint* path)
{ {
const Token *array = arrayToken; const Token *array = arrayToken;
while (Token::Match(array, ".|::")) while (Token::Match(array, ".|::"))
@ -210,6 +210,8 @@ static bool getDimensionsEtc(const Token * const arrayToken, const Settings *set
const ValueFlow::Value *value = getBufferSizeValue(array); const ValueFlow::Value *value = getBufferSizeValue(array);
if (!value) if (!value)
return false; return false;
if (path)
*path = value->path;
*errorPath = value->errorPath; *errorPath = value->errorPath;
Dimension dim; Dimension dim;
dim.known = value->isKnown(); dim.known = value->isKnown();
@ -223,7 +225,7 @@ static bool getDimensionsEtc(const Token * const arrayToken, const Settings *set
return !dimensions->empty(); return !dimensions->empty();
} }
static std::vector<const ValueFlow::Value *> getOverrunIndexValues(const Token *tok, const Token *arrayToken, const std::vector<Dimension> &dimensions, const std::vector<const Token *> &indexTokens) static std::vector<const ValueFlow::Value *> getOverrunIndexValues(const Token *tok, const Token *arrayToken, const std::vector<Dimension> &dimensions, const std::vector<const Token *> &indexTokens, MathLib::bigint path)
{ {
const Token *array = arrayToken; const Token *array = arrayToken;
while (Token::Match(array, ".|::")) while (Token::Match(array, ".|::"))
@ -239,6 +241,8 @@ static std::vector<const ValueFlow::Value *> getOverrunIndexValues(const Token *
indexValues.push_back(value); indexValues.push_back(value);
if (!value) if (!value)
continue; continue;
if (value->path != path)
continue;
if (!value->isKnown()) { if (!value->isKnown()) {
if (!allKnown) if (!allKnown)
continue; continue;
@ -303,12 +307,13 @@ void CheckBufferOverrun::arrayIndex()
std::vector<Dimension> dimensions; std::vector<Dimension> dimensions;
ErrorPath errorPath; ErrorPath errorPath;
bool mightBeLarger = false; bool mightBeLarger = false;
if (!getDimensionsEtc(tok->astOperand1(), mSettings, &dimensions, &errorPath, &mightBeLarger)) MathLib::bigint path = 0;
if (!getDimensionsEtc(tok->astOperand1(), mSettings, &dimensions, &errorPath, &mightBeLarger, &path))
continue; continue;
// Positive index // Positive index
if (!mightBeLarger) { // TODO check arrays with dim 1 also if (!mightBeLarger) { // TODO check arrays with dim 1 also
const std::vector<const ValueFlow::Value *> &indexValues = getOverrunIndexValues(tok, tok->astOperand1(), dimensions, indexTokens); const std::vector<const ValueFlow::Value *> &indexValues = getOverrunIndexValues(tok, tok->astOperand1(), dimensions, indexTokens, path);
if (!indexValues.empty()) if (!indexValues.empty())
arrayIndexError(tok, dimensions, indexValues); arrayIndexError(tok, dimensions, indexValues);
} }
@ -452,14 +457,15 @@ void CheckBufferOverrun::pointerArithmetic()
std::vector<Dimension> dimensions; std::vector<Dimension> dimensions;
ErrorPath errorPath; ErrorPath errorPath;
bool mightBeLarger = false; bool mightBeLarger = false;
if (!getDimensionsEtc(arrayToken, mSettings, &dimensions, &errorPath, &mightBeLarger)) MathLib::bigint path = 0;
if (!getDimensionsEtc(arrayToken, mSettings, &dimensions, &errorPath, &mightBeLarger, &path))
continue; continue;
if (tok->str() == "+") { if (tok->str() == "+") {
// Positive index // Positive index
if (!mightBeLarger) { // TODO check arrays with dim 1 also if (!mightBeLarger) { // TODO check arrays with dim 1 also
const std::vector<const Token *> indexTokens{indexToken}; const std::vector<const Token *> indexTokens{indexToken};
const std::vector<const ValueFlow::Value *> &indexValues = getOverrunIndexValues(tok, arrayToken, dimensions, indexTokens); const std::vector<const ValueFlow::Value *> &indexValues = getOverrunIndexValues(tok, arrayToken, dimensions, indexTokens, path);
if (!indexValues.empty()) if (!indexValues.empty())
pointerArithmeticError(tok, indexToken, indexValues.front()); pointerArithmeticError(tok, indexToken, indexValues.front());
} }

View File

@ -1646,6 +1646,8 @@ void Token::printValueFlow(bool xml, std::ostream &out) const
if (value.indirect > 0) if (value.indirect > 0)
for (int i=0; i<value.indirect; i++) for (int i=0; i<value.indirect; i++)
out << "*"; out << "*";
if (value.path > 0)
out << "@" << value.path;
} }
} }
if (xml) if (xml)

View File

@ -357,6 +357,10 @@ static void combineValueProperties(const ValueFlow::Value &value1, const ValueFl
if (value1.bound == ValueFlow::Value::Bound::Lower || value2.bound == ValueFlow::Value::Bound::Lower) if (value1.bound == ValueFlow::Value::Bound::Lower || value2.bound == ValueFlow::Value::Bound::Lower)
result->bound = ValueFlow::Value::Bound::Lower; result->bound = ValueFlow::Value::Bound::Lower;
} }
if (value1.path != value2.path)
result->path = -1;
else
result->path = value1.path;
} }
static const Token *getCastTypeStartToken(const Token *parent) static const Token *getCastTypeStartToken(const Token *parent)
@ -380,6 +384,9 @@ static void setTokenValue(Token* tok, const ValueFlow::Value &value, const Setti
if (!tok->addValue(value)) if (!tok->addValue(value))
return; return;
if (value.path < 0)
return;
Token *parent = tok->astParent(); Token *parent = tok->astParent();
if (!parent) if (!parent)
return; return;
@ -389,6 +396,8 @@ static void setTokenValue(Token* tok, const ValueFlow::Value &value, const Setti
if (parent->str() == "+") { if (parent->str() == "+") {
for (const ValueFlow::Value &value1 : parent->astOperand1()->values()) { for (const ValueFlow::Value &value1 : parent->astOperand1()->values()) {
for (const ValueFlow::Value &value2 : parent->astOperand2()->values()) { for (const ValueFlow::Value &value2 : parent->astOperand2()->values()) {
if (value1.path != value2.path)
continue;
ValueFlow::Value result; ValueFlow::Value result;
result.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE; result.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE;
if (value1.isContainerSizeValue() && value2.isContainerSizeValue()) if (value1.isContainerSizeValue() && value2.isContainerSizeValue())
@ -551,6 +560,8 @@ static void setTokenValue(Token* tok, const ValueFlow::Value &value, const Setti
if (value1.isTokValue() && (!parent->isComparisonOp() || value1.tokvalue->tokType() != Token::eString)) if (value1.isTokValue() && (!parent->isComparisonOp() || value1.tokvalue->tokType() != Token::eString))
continue; continue;
for (const ValueFlow::Value &value2 : parent->astOperand2()->values()) { for (const ValueFlow::Value &value2 : parent->astOperand2()->values()) {
if (value1.path != value2.path)
continue;
if (noninvertible && value2.isImpossible()) if (noninvertible && value2.isImpossible())
continue; continue;
if (!value2.isIntValue() && !value2.isFloatValue() && !value2.isTokValue()) if (!value2.isIntValue() && !value2.isFloatValue() && !value2.isTokValue())
@ -4793,65 +4804,73 @@ static void valueFlowLibraryFunction(Token *tok, const std::string &returnValue,
setTokenValues(tok, results, settings); setTokenValues(tok, results, settings);
} }
static void valueFlowSubFunction(TokenList* tokenlist, ErrorLogger* errorLogger, const Settings* settings) static void valueFlowSubFunction(TokenList* tokenlist, SymbolDatabase* symboldatabase, ErrorLogger* errorLogger, const Settings* settings)
{ {
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) { for (const Scope* scope : symboldatabase->functionScopes) {
if (!Token::Match(tok, "%name% (")) const Function* function = scope->function;
if (!function)
continue; continue;
int id = 0;
const Function * const calledFunction = tok->function(); for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
if (!calledFunction) { if (!Token::Match(tok, "%name% ("))
// library function?
const std::string& returnValue(settings->library.returnValue(tok));
if (!returnValue.empty())
valueFlowLibraryFunction(tok->next(), returnValue, settings);
continue;
}
const Scope * const calledFunctionScope = calledFunction->functionScope;
if (!calledFunctionScope)
continue;
// TODO: Rewrite this. It does not work well to inject 1 argument at a time.
const std::vector<const Token *> &callArguments = getArguments(tok);
for (int argnr = 0U; argnr < callArguments.size(); ++argnr) {
const Token *argtok = callArguments[argnr];
// Get function argument
const Variable * const argvar = calledFunction->getArgumentVar(argnr);
if (!argvar)
break;
// passing value(s) to function
std::list<ValueFlow::Value> argvalues(getFunctionArgumentValues(argtok));
// Don't forward lifetime values
argvalues.remove_if(std::mem_fn(&ValueFlow::Value::isLifetimeValue));
if (argvalues.empty())
continue; continue;
// Error path.. const Function * const calledFunction = tok->function();
for (ValueFlow::Value &v : argvalues) { if (!calledFunction) {
const std::string nr = MathLib::toString(argnr + 1) + getOrdinalText(argnr + 1); // library function?
const std::string& returnValue(settings->library.returnValue(tok));
v.errorPath.emplace_back(argtok, if (!returnValue.empty())
"Calling function '" + valueFlowLibraryFunction(tok->next(), returnValue, settings);
calledFunction->name() + continue;
"', " +
nr +
" argument '" +
argtok->expressionString() +
"' value is " +
v.infoString());
} }
// passed values are not "known".. const Scope * const calledFunctionScope = calledFunction->functionScope;
lowerToPossible(argvalues); if (!calledFunctionScope)
continue;
valueFlowInjectParameter(tokenlist, errorLogger, settings, argvar, calledFunctionScope, argvalues); id++;
// FIXME: We need to rewrite the valueflow analysis to better handle multiple arguments // TODO: Rewrite this. It does not work well to inject 1 argument at a time.
if (!argvalues.empty()) const std::vector<const Token *> &callArguments = getArguments(tok);
break; for (int argnr = 0U; argnr < callArguments.size(); ++argnr) {
const Token *argtok = callArguments[argnr];
// Get function argument
const Variable * const argvar = calledFunction->getArgumentVar(argnr);
if (!argvar)
break;
// passing value(s) to function
std::list<ValueFlow::Value> argvalues(getFunctionArgumentValues(argtok));
// Don't forward lifetime values
argvalues.remove_if(std::mem_fn(&ValueFlow::Value::isLifetimeValue));
if (argvalues.empty())
continue;
// Error path..
for (ValueFlow::Value &v : argvalues) {
const std::string nr = MathLib::toString(argnr + 1) + getOrdinalText(argnr + 1);
v.errorPath.emplace_back(argtok,
"Calling function '" +
calledFunction->name() +
"', " +
nr +
" argument '" +
argtok->expressionString() +
"' value is " +
v.infoString());
v.path = 256 * v.path + id;
}
// passed values are not "known"..
lowerToPossible(argvalues);
valueFlowInjectParameter(tokenlist, errorLogger, settings, argvar, calledFunctionScope, argvalues);
// FIXME: We need to rewrite the valueflow analysis to better handle multiple arguments
if (!argvalues.empty())
break;
}
} }
} }
} }
@ -5752,6 +5771,7 @@ ValueFlow::Value::Value(const Token* c, long long val)
conditional(false), conditional(false),
defaultArg(false), defaultArg(false),
indirect(0), indirect(0),
path(0),
lifetimeKind(LifetimeKind::Object), lifetimeKind(LifetimeKind::Object),
lifetimeScope(LifetimeScope::Local), lifetimeScope(LifetimeScope::Local),
valueKind(ValueKind::Possible) valueKind(ValueKind::Possible)
@ -5833,7 +5853,7 @@ void ValueFlow::setValues(TokenList *tokenlist, SymbolDatabase* symboldatabase,
valueFlowAfterAssign(tokenlist, symboldatabase, errorLogger, settings); valueFlowAfterAssign(tokenlist, symboldatabase, errorLogger, settings);
valueFlowSwitchVariable(tokenlist, symboldatabase, errorLogger, settings); valueFlowSwitchVariable(tokenlist, symboldatabase, errorLogger, settings);
valueFlowForLoop(tokenlist, symboldatabase, errorLogger, settings); valueFlowForLoop(tokenlist, symboldatabase, errorLogger, settings);
valueFlowSubFunction(tokenlist, errorLogger, settings); valueFlowSubFunction(tokenlist, symboldatabase, errorLogger, settings);
valueFlowFunctionDefaultParameter(tokenlist, symboldatabase, errorLogger, settings); valueFlowFunctionDefaultParameter(tokenlist, symboldatabase, errorLogger, settings);
valueFlowUninit(tokenlist, symboldatabase, errorLogger, settings); valueFlowUninit(tokenlist, symboldatabase, errorLogger, settings);
if (tokenlist->isCPP()) { if (tokenlist->isCPP()) {

View File

@ -22,6 +22,7 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "config.h" #include "config.h"
#include "mathlib.h"
#include "utils.h" #include "utils.h"
#include <list> #include <list>
@ -69,6 +70,7 @@ namespace ValueFlow {
conditional(false), conditional(false),
defaultArg(false), defaultArg(false),
indirect(0), indirect(0),
path(0),
lifetimeKind(LifetimeKind::Object), lifetimeKind(LifetimeKind::Object),
lifetimeScope(LifetimeScope::Local), lifetimeScope(LifetimeScope::Local),
valueKind(ValueKind::Possible) valueKind(ValueKind::Possible)
@ -247,6 +249,9 @@ namespace ValueFlow {
int indirect; int indirect;
/** Path id */
MathLib::bigint path;
enum class LifetimeKind {Object, Lambda, Iterator, Address} lifetimeKind; enum class LifetimeKind {Object, Lambda, Iterator, Address} lifetimeKind;
enum class LifetimeScope { Local, Argument } lifetimeScope; enum class LifetimeScope { Local, Argument } lifetimeScope;

View File

@ -128,6 +128,7 @@ private:
TEST_CASE(array_index_47); // #5849 TEST_CASE(array_index_47); // #5849
TEST_CASE(array_index_48); // #9478 TEST_CASE(array_index_48); // #9478
TEST_CASE(array_index_49); // #8653 TEST_CASE(array_index_49); // #8653
TEST_CASE(array_index_50);
TEST_CASE(array_index_multidim); TEST_CASE(array_index_multidim);
TEST_CASE(array_index_switch_in_for); TEST_CASE(array_index_switch_in_for);
TEST_CASE(array_index_for_in_for); // FP: #2634 TEST_CASE(array_index_for_in_for); // FP: #2634
@ -1509,6 +1510,18 @@ private:
ASSERT_EQUALS("", errout.str()); ASSERT_EQUALS("", errout.str());
} }
void array_index_50() {
check("void f(const char * str) {\n"
" int len = strlen(str);\n"
" (void)str[len - 1];\n"
"}\n"
"void g() {\n"
" f(\"12345678\");\n"
" f(\"12345\");\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
void array_index_multidim() { void array_index_multidim() {
check("void f()\n" check("void f()\n"
"{\n" "{\n"