diff --git a/.selfcheck_suppressions b/.selfcheck_suppressions index ba35bede7..c61108cdc 100644 --- a/.selfcheck_suppressions +++ b/.selfcheck_suppressions @@ -23,3 +23,5 @@ autoNoType bailoutUninitVar *:externals/*/* + +performanceValueflowMaxIfCountExceeded diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 06472f8cf..0eff7248e 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -606,6 +606,9 @@ bool CmdLineParser::parseFromArgs(int argc, const char* const argv[]) else if (std::strncmp(argv[i], "--performance-valueflow-max-time=", 33) == 0) mSettings.performanceValueFlowMaxTime = std::atoi(argv[i] + 33); + else if (std::strncmp(argv[i], "--performance-valueflow-max-if-count=", 37) == 0) + mSettings.performanceValueFlowMaxIfCount = std::atoi(argv[i] + 37); + // Specify platform else if (std::strncmp(argv[i], "--platform=", 11) == 0) { const std::string platform(11+argv[i]); @@ -1212,6 +1215,13 @@ void CmdLineParser::printHelp() " is 2. A larger value will mean more errors can be found\n" " but also means the analysis will be slower.\n" " --output-file= Write results to file, rather than standard error.\n" + " --performance-valueflow-max-if-count=\n" + " If you have many conditional scopes in a function then\n" + " the number of possible control flow paths through that\n" + " function explodes and that can lead to very long analysis\n" + " time. Valueflow is limited in functions that have more\n" + " than conditional scopes. The default limit is\n" + " 100, which few functions will reach.\n" " --platform=, --platform=\n" " Specifies platform specific types and sizes. The\n" " available builtin platforms are:\n" diff --git a/lib/settings.h b/lib/settings.h index 56d997659..6d1d9686e 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -254,6 +254,9 @@ public: /** @brief Experimental: --performance-valueflow-max-time=T */ int performanceValueFlowMaxTime; + /** @brief --performance-valueflow-max-if-count=C */ + int performanceValueFlowMaxIfCount = 100; + /** @brief plist output (--plist-output=<dir>) */ std::string plistOutput; diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 55abcaef6..259e991f7 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -5747,9 +5747,15 @@ static bool intersects(const C1& c1, const C2& c2) return false; } -static void valueFlowAfterAssign(TokenList *tokenlist, SymbolDatabase* symboldatabase, ErrorLogger *errorLogger, const Settings *settings) +static void valueFlowAfterAssign(TokenList *tokenlist, + SymbolDatabase* symboldatabase, + ErrorLogger *errorLogger, + const Settings *settings, + const std::set& skippedFunctions) { for (const Scope * scope : symboldatabase->functionScopes) { + if (skippedFunctions.count(scope)) + continue; std::unordered_map> backAssigns; for (Token* tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { // Assignment @@ -6051,9 +6057,12 @@ struct ConditionHandler { void traverseCondition(TokenList* tokenlist, SymbolDatabase* symboldatabase, const Settings* settings, + const std::set& skippedFunctions, const std::function& f) const { for (const Scope *scope : symboldatabase->functionScopes) { + if (skippedFunctions.count(scope)) + continue; for (Token *tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { if (Token::Match(tok, "if|while|for (")) continue; @@ -6086,8 +6095,9 @@ struct ConditionHandler { void beforeCondition(TokenList* tokenlist, SymbolDatabase* symboldatabase, ErrorLogger* errorLogger, - const Settings* settings) const { - traverseCondition(tokenlist, symboldatabase, settings, [&](const Condition& cond, Token* tok, const Scope*) { + const Settings* settings, + const std::set& skippedFunctions) const { + traverseCondition(tokenlist, symboldatabase, settings, skippedFunctions, [&](const Condition& cond, Token* tok, const Scope*) { if (cond.vartok->exprId() == 0) return; @@ -6233,8 +6243,9 @@ struct ConditionHandler { void afterCondition(TokenList* tokenlist, SymbolDatabase* symboldatabase, ErrorLogger* errorLogger, - const Settings* settings) const { - traverseCondition(tokenlist, symboldatabase, settings, [&](const Condition& cond, Token* condTok, const Scope* scope) { + const Settings* settings, + const std::set& skippedFunctions) const { + traverseCondition(tokenlist, symboldatabase, settings, skippedFunctions, [&](const Condition& cond, Token* condTok, const Scope* scope) { const Token* top = condTok->astTop(); const MathLib::bigint path = cond.getPath(); @@ -6564,10 +6575,11 @@ static void valueFlowCondition(const ValuePtr& handler, TokenList* tokenlist, SymbolDatabase* symboldatabase, ErrorLogger* errorLogger, - const Settings* settings) + const Settings* settings, + const std::set& skippedFunctions) { - handler->beforeCondition(tokenlist, symboldatabase, errorLogger, settings); - handler->afterCondition(tokenlist, symboldatabase, errorLogger, settings); + handler->beforeCondition(tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); + handler->afterCondition(tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); } struct SimpleConditionHandler : ConditionHandler { @@ -8966,6 +8978,36 @@ void ValueFlow::setValues(TokenList *tokenlist, SymbolDatabase* symboldatabase, const std::uint64_t stopTime = getValueFlowStopTime(settings); + std::set skippedFunctions; + if (settings->performanceValueFlowMaxIfCount < 1000) { + for (const Scope* functionScope: symboldatabase->functionScopes) { + int countIfScopes = 0; + std::vector scopes{functionScope}; + while (!scopes.empty()) { + const Scope* s = scopes.back(); + scopes.pop_back(); + for (const Scope* s2: s->nestedList) { + scopes.emplace_back(s2); + if (s2->type == Scope::ScopeType::eIf) + ++countIfScopes; + } + } + if (countIfScopes > settings->performanceValueFlowMaxIfCount) { + skippedFunctions.emplace(functionScope); + + const std::string& functionName = functionScope->className; + const std::list callstack(1, ErrorMessage::FileLocation(functionScope->bodyStart, tokenlist)); + const ErrorMessage errmsg(callstack, tokenlist->getSourceFilePath(), Severity::information, + "ValueFlow analysis is limited in " + functionName + " because if-count in function " + + std::to_string(countIfScopes) + " exceeds limit " + + std::to_string(settings->performanceValueFlowMaxIfCount) + ". The limit can be adjusted with " + "--performance-valueflow-max-if-count. Increasing the if-count limit will likely increase the " + "analysis time.", "performanceValueflowMaxIfCountExceeded", Certainty::normal); + errorLogger->reportErr(errmsg); + } + } + } + std::size_t values = 0; std::size_t n = settings->valueFlowMaxIterations; while (n > 0 && values != getTotalValues(tokenlist)) { @@ -8976,7 +9018,7 @@ void ValueFlow::setValues(TokenList *tokenlist, SymbolDatabase* symboldatabase, if (std::time(nullptr) < stopTime) valueFlowSymbolicOperators(symboldatabase, settings); if (std::time(nullptr) < stopTime) - valueFlowCondition(SymbolicConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings); + valueFlowCondition(SymbolicConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); if (std::time(nullptr) < stopTime) valueFlowSymbolicInfer(symboldatabase, settings); if (std::time(nullptr) < stopTime) @@ -8986,11 +9028,11 @@ void ValueFlow::setValues(TokenList *tokenlist, SymbolDatabase* symboldatabase, if (std::time(nullptr) < stopTime) valueFlowRightShift(tokenlist, settings); if (std::time(nullptr) < stopTime) - valueFlowAfterAssign(tokenlist, symboldatabase, errorLogger, settings); + valueFlowAfterAssign(tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); if (std::time(nullptr) < stopTime) valueFlowAfterSwap(tokenlist, symboldatabase, errorLogger, settings); if (std::time(nullptr) < stopTime) - valueFlowCondition(SimpleConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings); + valueFlowCondition(SimpleConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); if (std::time(nullptr) < stopTime) valueFlowInferCondition(tokenlist, settings); if (std::time(nullptr) < stopTime) @@ -9016,13 +9058,13 @@ void ValueFlow::setValues(TokenList *tokenlist, SymbolDatabase* symboldatabase, if (std::time(nullptr) < stopTime) valueFlowIterators(tokenlist, settings); if (std::time(nullptr) < stopTime) - valueFlowCondition(IteratorConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings); + valueFlowCondition(IteratorConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); if (std::time(nullptr) < stopTime) valueFlowIteratorInfer(tokenlist, settings); - if (std::time(nullptr) < stopTime) + if (std::time(nullptr) < stopTime && skippedFunctions.empty()) valueFlowContainerSize(tokenlist, symboldatabase, errorLogger, settings); if (std::time(nullptr) < stopTime) - valueFlowCondition(ContainerConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings); + valueFlowCondition(ContainerConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); } if (std::time(nullptr) < stopTime) valueFlowSafeFunctions(tokenlist, symboldatabase, settings);