Optimization: Improved performance of CheckBufferOverrun::checkScope() when dealing with a large number of arrays (#5975)
-> checking time decreases from 1010s to 50s on the code snippet in #5975 -> Dropped a garbage code unit test
This commit is contained in:
parent
ae97f53244
commit
8c0eab3eb3
|
@ -860,135 +860,176 @@ void CheckBufferOverrun::valueFlowCheckArrayIndex(const Token * const tok, const
|
||||||
|
|
||||||
void CheckBufferOverrun::checkScope(const Token *tok, const ArrayInfo &arrayInfo)
|
void CheckBufferOverrun::checkScope(const Token *tok, const ArrayInfo &arrayInfo)
|
||||||
{
|
{
|
||||||
assert(tok->previous() != nullptr);
|
|
||||||
const MathLib::biguint total_size = arrayInfo.num(0) * arrayInfo.element_size();
|
|
||||||
|
|
||||||
const unsigned int declarationId = arrayInfo.declarationId();
|
|
||||||
|
|
||||||
const bool printPortability = _settings->isEnabled("portability");
|
|
||||||
const bool printWarning = _settings->isEnabled("warning");
|
|
||||||
const bool printInconclusive = _settings->inconclusive;
|
|
||||||
|
|
||||||
bool reassigned = false;
|
bool reassigned = false;
|
||||||
|
|
||||||
for (const Token* const end = tok->scope()->classEnd; tok != end; tok = tok->next()) {
|
for (const Token* const end = tok->scope()->classEnd; tok != end; tok = tok->next()) {
|
||||||
if (reassigned && tok->str() == ";")
|
if (reassigned && tok->str() == ";")
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (tok->varId() == declarationId) {
|
if (tok->varId() != arrayInfo.declarationId())
|
||||||
if (tok->strAt(1) == "=") {
|
continue;
|
||||||
reassigned = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (tok->strAt(1) == "[") {
|
if (tok->strAt(1) == "=") {
|
||||||
valueFlowCheckArrayIndex(tok->next(), arrayInfo);
|
reassigned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (printPortability && !tok->isCast() && tok->astParent() && tok->astParent()->str() == "+") {
|
checkScope_inner(tok, arrayInfo);
|
||||||
// undefined behaviour: result of pointer arithmetic is out of bounds
|
}
|
||||||
const Token *index;
|
}
|
||||||
if (tok == tok->astParent()->astOperand1())
|
|
||||||
index = tok->astParent()->astOperand2();
|
|
||||||
else
|
|
||||||
index = tok->astParent()->astOperand1();
|
|
||||||
if (index) {
|
|
||||||
const ValueFlow::Value *value = index->getValueGE(arrayInfo.num(0) + 1U, _settings);
|
|
||||||
if (!value)
|
|
||||||
value = index->getValueLE(-1, _settings);
|
|
||||||
if (value)
|
|
||||||
pointerOutOfBoundsError(tok->astParent(), index, value->intvalue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (printPortability && tok->astParent() && tok->astParent()->str() == "-") {
|
void CheckBufferOverrun::checkScope(const Token *tok, std::map<unsigned int, ArrayInfo> arrayInfos)
|
||||||
const Variable *var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(declarationId);
|
{
|
||||||
if (var && var->isArray()) {
|
unsigned int reassigned = 0;
|
||||||
const Token *index = tok->astParent()->astOperand2();
|
|
||||||
const ValueFlow::Value *value = index ? index->getValueGE(1,_settings) : nullptr;
|
for (const Token* const end = tok->scope()->classEnd; tok != end; tok = tok->next()) {
|
||||||
if (index && !value)
|
if (reassigned && tok->str() == ";") {
|
||||||
value = index->getValueLE(-1 - arrayInfo.num(0), _settings);
|
arrayInfos.erase(reassigned);
|
||||||
if (value)
|
reassigned = 0;
|
||||||
pointerOutOfBoundsError(tok->astParent(), index, value->intvalue);
|
}
|
||||||
}
|
|
||||||
|
if (!tok->variable() || tok->variable()->nameToken() == tok)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::map<unsigned int, ArrayInfo>::const_iterator arrayInfo = arrayInfos.find(tok->varId());
|
||||||
|
if (arrayInfo == arrayInfos.cend())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (tok->strAt(1) == "=") {
|
||||||
|
reassigned = tok->varId();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkScope_inner(tok, arrayInfo->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckBufferOverrun::checkScope_inner(const Token *tok, const ArrayInfo &arrayInfo)
|
||||||
|
{
|
||||||
|
const bool printPortability = _settings->isEnabled("portability");
|
||||||
|
const bool printWarning = _settings->isEnabled("warning");
|
||||||
|
const bool printInconclusive = _settings->inconclusive;
|
||||||
|
|
||||||
|
if (tok->strAt(1) == "[") {
|
||||||
|
valueFlowCheckArrayIndex(tok->next(), arrayInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (printPortability && !tok->isCast() && tok->astParent() && tok->astParent()->str() == "+") {
|
||||||
|
// undefined behaviour: result of pointer arithmetic is out of bounds
|
||||||
|
const Token *index;
|
||||||
|
if (tok == tok->astParent()->astOperand1())
|
||||||
|
index = tok->astParent()->astOperand2();
|
||||||
|
else
|
||||||
|
index = tok->astParent()->astOperand1();
|
||||||
|
if (index) {
|
||||||
|
const ValueFlow::Value *value = index->getValueGE(arrayInfo.num(0) + 1U, _settings);
|
||||||
|
if (!value)
|
||||||
|
value = index->getValueLE(-1, _settings);
|
||||||
|
if (value)
|
||||||
|
pointerOutOfBoundsError(tok->astParent(), index, value->intvalue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (printPortability && tok->astParent() && tok->astParent()->str() == "-") {
|
||||||
|
const Variable *var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(arrayInfo.declarationId());
|
||||||
|
if (var && var->isArray()) {
|
||||||
|
const Token *index = tok->astParent()->astOperand2();
|
||||||
|
const ValueFlow::Value *value = index ? index->getValueGE(1,_settings) : nullptr;
|
||||||
|
if (index && !value)
|
||||||
|
value = index->getValueLE(-1 - arrayInfo.num(0), _settings);
|
||||||
|
if (value)
|
||||||
|
pointerOutOfBoundsError(tok->astParent(), index, value->intvalue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tok->scope()->isExecutable()) // No executable code outside of executable scope - continue to increase performance
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Token* tok2 = tok->astParent();
|
||||||
|
if (tok2) {
|
||||||
|
while (tok2->astParent() && !Token::Match(tok2->astParent(), "[,(]"))
|
||||||
|
tok2 = tok2->astParent();
|
||||||
|
while (tok2->astParent() && tok2->astParent()->str() == ",")
|
||||||
|
tok2 = tok2->astParent();
|
||||||
|
if (tok2->astParent() && tok2->astParent()->str() == "(")
|
||||||
|
tok2 = tok2->astParent();
|
||||||
|
|
||||||
|
if (tok2->str() != "(")
|
||||||
|
return;
|
||||||
|
|
||||||
|
tok2 = tok2->previous();
|
||||||
|
|
||||||
|
// Check function call..
|
||||||
|
checkFunctionCall(tok2, arrayInfo, std::list<const Token*>());
|
||||||
|
|
||||||
|
const MathLib::biguint total_size = arrayInfo.num(0) * arrayInfo.element_size();
|
||||||
|
|
||||||
|
if (printWarning && printInconclusive && Token::Match(tok2, "strncpy|memcpy|memmove ( %varid% , %str% , %num% )", arrayInfo.declarationId())) {
|
||||||
|
if (Token::getStrLength(tok2->tokAt(4)) >= total_size) {
|
||||||
|
const MathLib::biguint num = MathLib::toULongNumber(tok2->strAt(6));
|
||||||
|
if (total_size == num)
|
||||||
|
bufferNotZeroTerminatedError(tok2, tok2->strAt(2), tok2->str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tok->scope()->isExecutable()) // No executable code outside of executable scope - continue to increase performance
|
if (printWarning && Token::Match(tok2, "strncpy|strncat ( %varid% ,", arrayInfo.declarationId()) && Token::Match(tok2->linkAt(1)->tokAt(-2), ", %num% )")) {
|
||||||
continue;
|
const Token* param3 = tok2->linkAt(1)->previous();
|
||||||
|
|
||||||
else if (tok->next() && tok->next()->link() && Token::Match(tok, "%name% (")) {
|
// check for strncpy which is not terminated
|
||||||
// Check function call..
|
if (tok2->str() == "strncpy") {
|
||||||
checkFunctionCall(tok, arrayInfo, std::list<const Token*>());
|
// strncpy takes entire variable length as input size
|
||||||
|
const MathLib::biguint num = MathLib::toULongNumber(param3->str());
|
||||||
|
|
||||||
if (printWarning && printInconclusive && Token::Match(tok, "strncpy|memcpy|memmove ( %varid% , %str% , %num% )", declarationId)) {
|
// this is currently 'inconclusive'. See TestBufferOverrun::terminateStrncpy3
|
||||||
if (Token::getStrLength(tok->tokAt(4)) >= total_size) {
|
if (printInconclusive && num >= total_size) {
|
||||||
const MathLib::biguint num = MathLib::toULongNumber(tok->strAt(6));
|
const Token *tok4 = tok2->next()->link()->next();
|
||||||
if (total_size == num)
|
for (; tok4; tok4 = tok4->next()) {
|
||||||
bufferNotZeroTerminatedError(tok, tok->strAt(2), tok->str());
|
const Token* tok3 = tok2->tokAt(2);
|
||||||
}
|
if (tok4->varId() == tok3->varId()) {
|
||||||
}
|
if (!Token::Match(tok4, "%varid% [ %any% ] = 0 ;", tok3->varId())) {
|
||||||
|
terminateStrncpyError(tok2, tok3->str());
|
||||||
if (printWarning && Token::Match(tok, "strncpy|strncat ( %varid% ,", declarationId) && Token::Match(tok->linkAt(1)->tokAt(-2), ", %num% )")) {
|
|
||||||
const Token* param3 = tok->linkAt(1)->previous();
|
|
||||||
|
|
||||||
// check for strncpy which is not terminated
|
|
||||||
if (tok->str() == "strncpy") {
|
|
||||||
// strncpy takes entire variable length as input size
|
|
||||||
const MathLib::biguint num = MathLib::toULongNumber(param3->str());
|
|
||||||
|
|
||||||
// this is currently 'inconclusive'. See TestBufferOverrun::terminateStrncpy3
|
|
||||||
if (printInconclusive && num >= total_size) {
|
|
||||||
const Token *tok2 = tok->next()->link()->next();
|
|
||||||
for (; tok2; tok2 = tok2->next()) {
|
|
||||||
const Token* tok3 = tok->tokAt(2);
|
|
||||||
if (tok2->varId() == tok3->varId()) {
|
|
||||||
if (!Token::Match(tok2, "%varid% [ %any% ] = 0 ;", tok3->varId())) {
|
|
||||||
terminateStrncpyError(tok, tok3->str());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dangerous usage of strncat..
|
// Dangerous usage of strncat..
|
||||||
else if (tok->str() == "strncat") {
|
else if (tok2->str() == "strncat") {
|
||||||
const MathLib::biguint n = MathLib::toULongNumber(param3->str());
|
const MathLib::biguint n = MathLib::toULongNumber(param3->str());
|
||||||
if (n >= total_size)
|
if (n >= total_size)
|
||||||
strncatUsageError(tok);
|
strncatUsageError(tok2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dangerous usage of strncpy + strncat..
|
// Dangerous usage of strncpy + strncat..
|
||||||
if (Token::Match(param3->tokAt(2), "; strncat ( %varid% ,", declarationId) && Token::Match(param3->linkAt(4)->tokAt(-2), ", %num% )")) {
|
if (Token::Match(param3->tokAt(2), "; strncat ( %varid% ,", arrayInfo.declarationId()) && Token::Match(param3->linkAt(4)->tokAt(-2), ", %num% )")) {
|
||||||
const MathLib::biguint n = MathLib::toULongNumber(param3->str()) + MathLib::toULongNumber(param3->linkAt(4)->strAt(-1));
|
const MathLib::biguint n = MathLib::toULongNumber(param3->str()) + MathLib::toULongNumber(param3->linkAt(4)->strAt(-1));
|
||||||
if (n > total_size)
|
if (n > total_size)
|
||||||
strncatUsageError(param3->tokAt(3));
|
strncatUsageError(param3->tokAt(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing data into array..
|
||||||
|
if (total_size > 0) {
|
||||||
|
if (Token::Match(tok2, "strcpy ( %varid% , %str% )", arrayInfo.declarationId())) {
|
||||||
|
const std::size_t len = Token::getStrLength(tok2->tokAt(4));
|
||||||
|
if (len >= total_size) {
|
||||||
|
bufferOverrunError(tok2, arrayInfo.varname());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writing data into array..
|
// Detect few strcat() calls
|
||||||
if (total_size > 0) {
|
MathLib::biguint charactersAppend = 0;
|
||||||
if (Token::Match(tok, "strcpy ( %varid% , %str% )", declarationId)) {
|
const Token *tok3 = tok2;
|
||||||
const std::size_t len = Token::getStrLength(tok->tokAt(4));
|
|
||||||
if (len >= total_size) {
|
|
||||||
bufferOverrunError(tok, arrayInfo.varname());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect few strcat() calls
|
while (Token::Match(tok3, "strcat ( %varid% , %str% ) ;", arrayInfo.declarationId())) {
|
||||||
MathLib::biguint charactersAppend = 0;
|
charactersAppend += Token::getStrLength(tok3->tokAt(4));
|
||||||
const Token *tok2 = tok;
|
if (charactersAppend >= total_size) {
|
||||||
|
bufferOverrunError(tok3, arrayInfo.varname());
|
||||||
while (Token::Match(tok2, "strcat ( %varid% , %str% ) ;", declarationId)) {
|
break;
|
||||||
charactersAppend += Token::getStrLength(tok2->tokAt(4));
|
|
||||||
if (charactersAppend >= total_size) {
|
|
||||||
bufferOverrunError(tok2, arrayInfo.varname());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tok2 = tok2->tokAt(7);
|
|
||||||
}
|
}
|
||||||
|
tok3 = tok3->tokAt(7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1127,33 +1168,36 @@ void CheckBufferOverrun::checkGlobalAndLocalVariable()
|
||||||
|
|
||||||
// check all known fixed size arrays first by just looking them up
|
// check all known fixed size arrays first by just looking them up
|
||||||
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
|
||||||
for (unsigned int i = 1; i <= _tokenizer->varIdCount(); i++) {
|
for (std::list<Scope>::const_iterator scope = symbolDatabase->scopeList.cbegin(); scope != symbolDatabase->scopeList.cend(); ++scope) {
|
||||||
const Variable * const var = symbolDatabase->getVariableFromVarId(i);
|
std::map<unsigned int, ArrayInfo> arrayInfos;
|
||||||
if (var && var->isArray() && var->dimension(0) > 0) {
|
for (std::list<Variable>::const_iterator var = scope->varlist.cbegin(); var != scope->varlist.cend(); ++var) {
|
||||||
_errorLogger->reportProgress(_tokenizer->list.getSourceFilePath(),
|
if (var->isArray() && var->dimension(0) > 0) {
|
||||||
"Check (BufferOverrun::checkGlobalAndLocalVariable 1)",
|
_errorLogger->reportProgress(_tokenizer->list.getSourceFilePath(),
|
||||||
var->nameToken()->progressValue());
|
"Check (BufferOverrun::checkGlobalAndLocalVariable 1)",
|
||||||
|
var->nameToken()->progressValue());
|
||||||
|
|
||||||
if (_tokenizer->isMaxTime())
|
if (_tokenizer->isMaxTime())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const Token *tok = var->nameToken();
|
const Token *tok = var->nameToken();
|
||||||
do {
|
do {
|
||||||
if (tok->str() == "{") {
|
if (tok->str() == "{") {
|
||||||
if (Token::simpleMatch(tok->previous(), "= {"))
|
if (Token::simpleMatch(tok->previous(), "= {"))
|
||||||
tok = tok->link();
|
tok = tok->link();
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
tok = tok->next();
|
tok = tok->next();
|
||||||
} while (tok && tok->str() != ";");
|
} while (tok && tok->str() != ";");
|
||||||
if (!tok)
|
if (!tok)
|
||||||
break;
|
break;
|
||||||
if (tok->str() == "{")
|
if (tok->str() == "{")
|
||||||
tok = tok->next();
|
tok = tok->next();
|
||||||
const ArrayInfo arrayInfo(var, _tokenizer, &_settings->library, i);
|
arrayInfos[var->declarationId()] = ArrayInfo(&*var, _tokenizer, &_settings->library, var->declarationId());
|
||||||
checkScope(tok, arrayInfo);
|
}
|
||||||
}
|
}
|
||||||
|
if (!arrayInfos.empty())
|
||||||
|
checkScope(scope->classStart ? scope->classStart : _tokenizer->tokens(), arrayInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// find all dynamically allocated arrays next
|
// find all dynamically allocated arrays next
|
||||||
|
|
|
@ -172,6 +172,8 @@ public:
|
||||||
|
|
||||||
/** Check for buffer overruns (based on ArrayInfo) */
|
/** Check for buffer overruns (based on ArrayInfo) */
|
||||||
void checkScope(const Token *tok, const ArrayInfo &arrayInfo);
|
void checkScope(const Token *tok, const ArrayInfo &arrayInfo);
|
||||||
|
void checkScope(const Token *tok, std::map<unsigned int, ArrayInfo> arrayInfo);
|
||||||
|
void checkScope_inner(const Token *tok, const ArrayInfo &arrayInfo);
|
||||||
|
|
||||||
/** Check for buffer overruns */
|
/** Check for buffer overruns */
|
||||||
void checkScope(const Token *tok, const std::vector<std::string> &varname, const ArrayInfo &arrayInfo);
|
void checkScope(const Token *tok, const std::vector<std::string> &varname, const ArrayInfo &arrayInfo);
|
||||||
|
|
|
@ -227,8 +227,6 @@ private:
|
||||||
|
|
||||||
TEST_CASE(getErrorMessages);
|
TEST_CASE(getErrorMessages);
|
||||||
|
|
||||||
TEST_CASE(unknownMacroNoDecl); // #2638 - not variable declaration: 'AAA a[0] = 0;'
|
|
||||||
|
|
||||||
// Access array and then check if the used index is within bounds
|
// Access array and then check if the used index is within bounds
|
||||||
TEST_CASE(arrayIndexThenCheck);
|
TEST_CASE(arrayIndexThenCheck);
|
||||||
|
|
||||||
|
@ -3749,15 +3747,6 @@ private:
|
||||||
c.getErrorMessages(this, 0);
|
c.getErrorMessages(this, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unknownMacroNoDecl() {
|
|
||||||
check("void f() {\n"
|
|
||||||
" int a[10];\n"
|
|
||||||
" AAA a[0] = 0;\n" // <- not a valid array declaration
|
|
||||||
" a[1] = 1;\n"
|
|
||||||
"}");
|
|
||||||
ASSERT_EQUALS("", errout.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void arrayIndexThenCheck() {
|
void arrayIndexThenCheck() {
|
||||||
check("void f(const char s[]) {\n"
|
check("void f(const char s[]) {\n"
|
||||||
" if (s[i] == 'x' && i < y) {\n"
|
" if (s[i] == 'x' && i < y) {\n"
|
||||||
|
|
Loading…
Reference in New Issue