From ca7ced49535fea27073fd3ac3fbee0ebeab0cd5b Mon Sep 17 00:00:00 2001 From: wy Date: Wed, 20 Aug 2025 18:16:55 +0800 Subject: [PATCH] Fix #12861 Hang in valueFlowCondition() with huge array --- lib/astutils.cpp | 12 +++++++----- lib/checkautovariables.cpp | 4 ++-- lib/checkclass.cpp | 4 ++-- lib/checkcondition.cpp | 6 +++--- lib/checkfunctions.cpp | 2 +- lib/checkleakautovar.cpp | 2 +- lib/checkmemoryleak.cpp | 2 +- lib/checkother.cpp | 6 +++--- lib/checkstl.cpp | 4 ++-- lib/forwardanalyzer.cpp | 2 +- lib/library.cpp | 4 ++-- lib/programmemory.cpp | 6 ++++-- lib/reverseanalyzer.cpp | 2 +- lib/symboldatabase.cpp | 2 +- lib/token.cpp | 4 ++-- lib/token.h | 19 +++++++++++++++++-- lib/tokenlist.cpp | 2 +- lib/valueflow.cpp | 18 +++++++++--------- test/testtokenize.cpp | 8 ++++---- 19 files changed, 64 insertions(+), 45 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 30f10794855..0c2134d077e 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1013,7 +1013,9 @@ static const Token * getVariableInitExpression(const Variable * var) const Token* isInLoopCondition(const Token* tok) { - const Token* top = tok->astTop(); + if (!tok) + return nullptr; + const Token* top = tok->astTop(true); return Token::Match(top->previous(), "for|while (") ? top : nullptr; } @@ -2243,12 +2245,12 @@ bool isReturnScope(const Token* const endToken, const Library& library, const To !Token::findsimplematch(prev->link(), "break", prev)) { return isReturnScope(prev, library, unknownFunc, functionScope); } - if (isEscaped(prev->link()->astTop(), functionScope, library)) + if (isEscaped(prev->link()->astTop(true), functionScope, library)) return true; if (Token::Match(prev->link()->previous(), "[;{}] {")) return isReturnScope(prev, library, unknownFunc, functionScope); } else if (Token::simpleMatch(prev, ";")) { - if (prev->tokAt(-2) && hasNoreturnFunction(prev->tokAt(-2)->astTop(), library, unknownFunc)) + if (prev->tokAt(-2) && hasNoreturnFunction(prev->tokAt(-2)->astTop(true), library, unknownFunc)) return true; // Unknown symbol if (Token::Match(prev->tokAt(-2), ";|}|{ %name% ;") && prev->previous()->isIncompleteVar()) { @@ -2257,9 +2259,9 @@ bool isReturnScope(const Token* const endToken, const Library& library, const To return false; } if (Token::simpleMatch(prev->previous(), ") ;") && prev->linkAt(-1) && - isEscaped(prev->linkAt(-1)->astTop(), functionScope, library)) + isEscaped(prev->linkAt(-1)->astTop(true), functionScope, library)) return true; - if (isEscaped(prev->previous()->astTop(), functionScope, library)) + if (isEscaped(prev->previous()->astTop(true), functionScope, library)) return true; // return/goto statement prev = prev->previous(); diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index 8b9023d9473..4098c3c1f9f 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -468,7 +468,7 @@ static bool isDeadTemporary(const Token* tok, const Token* expr, const Library& if (!isTemporary(tok, &library)) return false; if (expr) { - if (!precedes(nextAfterAstRightmostLeaf(tok->astTop()), nextAfterAstRightmostLeaf(expr->astTop()))) + if (!precedes(nextAfterAstRightmostLeaf(tok->astTop(true)), nextAfterAstRightmostLeaf(expr->astTop(true)))) return false; const Token* parent = tok->astParent(); if (Token::simpleMatch(parent, "{") && Token::simpleMatch(parent->astParent(), ":")) @@ -642,7 +642,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token } if (!ValueFlow::isLifetimeBorrowed(tok, *mSettings)) continue; - const Token* nextTok = nextAfterAstRightmostLeaf(tok->astTop()); + const Token* nextTok = nextAfterAstRightmostLeaf(tok->astTop(true)); if (!nextTok) nextTok = tok->next(); if (var && (!var->isLocal() || var->isStatic()) && !var->isArgument() && !(val.tokvalue && val.tokvalue->variable() && val.tokvalue->variable()->isStatic()) && diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index 5a6469ae8fb..400f36633ea 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -1877,7 +1877,7 @@ CheckClass::Bool CheckClass::isInverted(const Token *tok, const Token *rhs) const Token * CheckClass::getIfStmtBodyStart(const Token *tok, const Token *rhs) { - const Token *top = tok->astTop(); + const Token *top = tok->astTop(true); if (Token::simpleMatch(top->link(), ") {")) { switch (isInverted(tok->astParent(), rhs)) { case Bool::BAILOUT: @@ -2522,7 +2522,7 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member return false; parent = parent->astParent(); } - const Token* const top = lhs->astTop(); + const Token* const top = lhs->astTop(true); if (top->isAssignmentOp()) { if (Token::simpleMatch(top->astOperand2(), "{") && !top->astOperand2()->previous()->function()) // TODO: check usage in init list return false; diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index b5316dbee00..c01775aada0 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -856,7 +856,7 @@ static std::string innerSmtString(const Token * tok) { if (!tok) return "if"; - const Token * top = tok->astTop(); + const Token * top = tok->astTop(true); if (top->str() == "(" && top->astOperand1()) return top->astOperand1()->str(); return top->str(); @@ -1138,7 +1138,7 @@ static std::string conditionString(const Token * tok) } static bool isIfConstexpr(const Token* tok) { - const Token* const top = tok->astTop(); + const Token* const top = tok->astTop(true); return Token::simpleMatch(top->astOperand1(), "if") && top->astOperand1()->isConstexpr(); } @@ -1835,7 +1835,7 @@ void CheckCondition::checkDuplicateConditionalAssign() continue; if (!blockTok->next()) continue; - const Token *assignTok = blockTok->next()->astTop(); + const Token *assignTok = blockTok->next()->astTop(true); if (!Token::simpleMatch(assignTok, "=")) continue; if (nextAfterAstRightmostLeaf(assignTok) != blockTok->link()->previous()) diff --git a/lib/checkfunctions.cpp b/lib/checkfunctions.cpp index 8884a03fc48..aa7cb18d59b 100644 --- a/lib/checkfunctions.cpp +++ b/lib/checkfunctions.cpp @@ -641,7 +641,7 @@ void CheckFunctions::checkLibraryMatchFunctions() if (tok->function()) continue; - if (Token::simpleMatch(tok->astTop(), "throw")) + if (Token::simpleMatch(tok->astTop(true), "throw")) continue; if (Token::simpleMatch(tok->astParent(), ".")) { diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index 17f5629b009..0f81ecc23b4 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -985,7 +985,7 @@ void CheckLeakAutoVar::changeAllocStatus(VarInfo &varInfo, const VarInfo::AllocI var->second.type = allocation.type; var->second.allocTok = allocation.allocTok; } - } else if (allocation.status != VarInfo::NOALLOC && allocation.status != VarInfo::OWNED && !Token::simpleMatch(tok->astTop(), "return")) { + } else if (allocation.status != VarInfo::NOALLOC && allocation.status != VarInfo::OWNED && !Token::simpleMatch(tok->astTop(true), "return")) { auto& allocInfo = alloctype[arg->varId()]; allocInfo.status = VarInfo::DEALLOC; allocInfo.allocTok = tok; diff --git a/lib/checkmemoryleak.cpp b/lib/checkmemoryleak.cpp index 04a94ea31a5..756c33d93a3 100644 --- a/lib/checkmemoryleak.cpp +++ b/lib/checkmemoryleak.cpp @@ -1012,7 +1012,7 @@ void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope) tok2 = tok2->astParent(); if (Token::Match(tok2, "%assign%")) // TODO: check if function returns allocated resource continue; - if (Token::simpleMatch(tok->astTop(), "return")) + if (Token::simpleMatch(tok->astTop(true), "return")) continue; const std::string& functionName = tok->str(); diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 7a06f66f644..3a1cdc8aeec 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -2241,7 +2241,7 @@ static bool isConstTop(const Token *tok) if (!tok->astParent()) return true; if (Token::simpleMatch(tok->astParent(), ";") && - Token::Match(tok->astTop()->previous(), "for|if (") && Token::simpleMatch(tok->astTop()->astOperand2(), ";")) { + Token::Match(tok->astTop(true)->previous(), "for|if (") && Token::simpleMatch(tok->astTop(true)->astOperand2(), ";")) { if (Token::simpleMatch(tok->astParent()->astParent(), ";")) return tok->astParent()->astOperand2() == tok; return tok->astParent()->astOperand1() == tok; @@ -2272,7 +2272,7 @@ void CheckOther::checkIncompleteStatement() continue; if (!isConstTop(tok)) continue; - if (tok->str() == "," && Token::simpleMatch(tok->astTop()->previous(), "for (")) + if (tok->str() == "," && Token::simpleMatch(tok->astTop(true)->previous(), "for (")) continue; // Do not warn for statement when both lhs and rhs has side effects: @@ -2540,7 +2540,7 @@ static const Token * getSingleExpressionInBlock(const Token * tok) { if (!tok) return nullptr; - const Token * top = tok->astTop(); + const Token * top = tok->astTop(true); const Token * nextExpression = nextAfterAstRightmostLeaf(top); if (!Token::simpleMatch(nextExpression, "; }")) return nullptr; diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp index a88591a904a..e9b023e41cd 100644 --- a/lib/checkstl.cpp +++ b/lib/checkstl.cpp @@ -1649,7 +1649,7 @@ static const Token* skipLocalVars(const Token* const tok) return skipLocalVars(tok->next()); if (tok->isAssignmentOp()) { - const Token *top = tok->astTop(); + const Token *top = tok->astTop(true); const Token *varTok = top->astOperand1(); const Variable *var = varTok->variable(); if (!var) @@ -1667,7 +1667,7 @@ static const Token* skipLocalVars(const Token* const tok) static const Token *findInsertValue(const Token *tok, const Token *containerTok, const Token *keyTok, const Settings &settings) { const Token *startTok = skipLocalVars(tok); - const Token *top = startTok->astTop(); + const Token *top = startTok->astTop(true); const Token *icontainerTok = nullptr; const Token *ikeyTok = nullptr; diff --git a/lib/forwardanalyzer.cpp b/lib/forwardanalyzer.cpp index ebdfc70a50d..6cc54ad7717 100644 --- a/lib/forwardanalyzer.cpp +++ b/lib/forwardanalyzer.cpp @@ -610,7 +610,7 @@ namespace { // In the middle of a loop structure so bail return Break(Analyzer::Terminate::Bail); } else if (tok->str() == ";" && tok->astParent()) { - Token* top = tok->astTop(); + Token* top = tok->astTop(true); if (Token::Match(top->previous(), "for|while (") && Token::simpleMatch(top->link(), ") {")) { Token* endCond = top->link(); Token* endBlock = endCond->linkAt(1); diff --git a/lib/library.cpp b/lib/library.cpp index f786584bdf2..e2a390ba2f6 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -1338,7 +1338,7 @@ bool Library::isScopeNoReturn(const Token *end, std::string *unknownFunc) const unknownFunc->clear(); if (Token::Match(end->tokAt(-2), "!!{ ; }")) { - const Token *lastTop = end->tokAt(-2)->astTop(); + const Token *lastTop = end->tokAt(-2)->astTop(true); if (Token::simpleMatch(lastTop, "<<") && Token::simpleMatch(lastTop->astOperand1(), "(") && Token::Match(lastTop->astOperand1()->previous(), "%name% (")) @@ -1349,7 +1349,7 @@ bool Library::isScopeNoReturn(const Token *end, std::string *unknownFunc) const return false; const Token *funcname = end->linkAt(-2)->previous(); - if (funcname->isCpp() && funcname->astTop()->str() == "throw") + if (funcname->isCpp() && funcname->astTop(true)->str() == "throw") return true; const Token *start = funcname; if (Token::Match(funcname->tokAt(-3),"( * %name% )")) { diff --git a/lib/programmemory.cpp b/lib/programmemory.cpp index df4b5fcc0c7..608ecdbf73d 100644 --- a/lib/programmemory.cpp +++ b/lib/programmemory.cpp @@ -524,13 +524,15 @@ void ProgramMemoryState::addState(const Token* tok, const ProgramMemory::Map& va void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty) { + if (!tok) + return; ProgramMemory pm = state; if (isEmpty) pm.setContainerSizeValue(tok, 0, b); else programMemoryParseCondition(pm, tok, nullptr, settings, b); const Token* origin = tok; - const Token* top = tok->astTop(); + const Token* top = tok->astTop(true); if (Token::Match(top->previous(), "for|while|if (") && !Token::simpleMatch(tok->astParent(), "?")) { origin = top->link()->next(); if (!b && origin->link()) { @@ -1748,7 +1750,7 @@ namespace { if (!scope->bodyStart) return {unknown()}; for (const Token* tok = scope->bodyStart->next(); precedes(tok, scope->bodyEnd); tok = tok->next()) { - const Token* top = tok->astTop(); + const Token* top = tok->astTop(true); if (Token::simpleMatch(top, "return") && top->astOperand1()) return {execute(top->astOperand1())}; diff --git a/lib/reverseanalyzer.cpp b/lib/reverseanalyzer.cpp index f1318295d6a..47b01fd03b4 100644 --- a/lib/reverseanalyzer.cpp +++ b/lib/reverseanalyzer.cpp @@ -295,7 +295,7 @@ namespace { if (!condTok) break; Analyzer::Action condAction = analyzeRecursive(condTok); - const bool inLoop = Token::Match(condTok->astTop()->previous(), "for|while ("); + const bool inLoop = Token::Match(condTok->astTop(true)->previous(), "for|while ("); // Evaluate condition of for and while loops first if (inLoop) { if (Token::findmatch(tok->link(), "goto|break", tok)) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index e05001b6896..7f36e1021de 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -5369,7 +5369,7 @@ const Enumerator * SymbolDatabase::findEnumerator(const Token * tok, std::setscope()->type == ScopeType::eGlobal) { - const Token* astTop = tok->astTop(); + const Token* astTop = tok->astTop(true); if (Token::simpleMatch(astTop, ":") && Token::simpleMatch(astTop->astOperand1(), "(")) { // ctor init list const Token* ctor = astTop->astOperand1()->previous(); if (ctor && ctor->function() && ctor->function()->nestedIn) diff --git a/lib/token.cpp b/lib/token.cpp index 44e62e21507..07af2d47001 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -1437,7 +1437,7 @@ void Token::astOperand1(Token *tok) mImpl->mAstOperand1->astParent(nullptr); // goto parent operator if (tok) { - tok = tok->astTop(); + tok = tok->astTop(false); tok->astParent(this); } mImpl->mAstOperand1 = tok; @@ -1449,7 +1449,7 @@ void Token::astOperand2(Token *tok) mImpl->mAstOperand2->astParent(nullptr); // goto parent operator if (tok) { - tok = tok->astTop(); + tok = tok->astTop(false); tok->astParent(this); } mImpl->mAstOperand2 = tok; diff --git a/lib/token.h b/lib/token.h index b2c433af164..1f5ea170e5f 100644 --- a/lib/token.h +++ b/lib/token.h @@ -88,6 +88,7 @@ struct TokenImpl { Token* mAstOperand1{}; Token* mAstOperand2{}; Token* mAstParent{}; + Token* mAstTop{}; // symbol database information const Scope* mScope{}; @@ -1531,17 +1532,31 @@ class CPPCHECKLIB Token { return nullptr; } - RET_NONNULL Token *astTop() { + /** If the ast tree has not been created, pls make sure to use cache=false, + as the real top has not been identified finally. */ + RET_NONNULL Token *astTop(bool cache) { Token *ret = this; + if (cache && mImpl->mAstTop) { + return mImpl->mAstTop; + } while (ret->mImpl->mAstParent) ret = ret->mImpl->mAstParent; + if (cache) { + mImpl->mAstTop = ret; + } return ret; } - RET_NONNULL const Token *astTop() const { + RET_NONNULL const Token *astTop(bool cache) const { const Token *ret = this; + if (cache && mImpl->mAstTop) { + return mImpl->mAstTop; + } while (ret->mImpl->mAstParent) ret = ret->mImpl->mAstParent; + if (cache) { + mImpl->mAstTop = const_cast(ret); + } return ret; } diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp index 678abeafff5..ced9095458c 100644 --- a/lib/tokenlist.cpp +++ b/lib/tokenlist.cpp @@ -1728,7 +1728,7 @@ static Token * createAstAtToken(Token *tok) } if (init != semicolon1) - semicolon1->astOperand1(init->astTop()); + semicolon1->astOperand1(init->astTop(false)); tok->next()->astOperand1(tok); tok->next()->astOperand2(semicolon1); diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 9799bb3e852..2d9f5211f59 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -1575,7 +1575,7 @@ static bool isConditionKnown(const Token* tok, bool then) const Token* parent = tok->astParent(); while (parent && (parent->str() == op || parent->str() == "!" || parent->isCast())) parent = parent->astParent(); - const Token* top = tok->astTop(); + const Token* top = tok->astTop(true); if (Token::Match(top->previous(), "if|while|for (")) return parent == top || Token::simpleMatch(parent, ";"); return parent && parent->str() != op; @@ -2105,7 +2105,7 @@ const Token* ValueFlow::getEndOfExprScope(const Token* tok, const Scope* default if (!end || (smallest ? precedes(varEnd, end) : succeeds(varEnd, end))) end = varEnd; - const Token* top = var->nameToken()->astTop(); + const Token* top = var->nameToken()->astTop(true); if (Token::simpleMatch(top->tokAt(-1), "if (")) { // variable declared in if (...) const Token* elseTok = top->link()->linkAt(1); if (Token::simpleMatch(elseTok, "} else {") && tok->scope()->isNestedIn(elseTok->tokAt(2)->scope())) @@ -4599,7 +4599,7 @@ struct ConditionHandler { if (Token::Match(tok, ":|;|,")) continue; - const Token* top = tok->astTop(); + const Token* top = tok->astTop(true); if (!Token::Match(top->previous(), "if|while|for (") && !Token::Match(tok->astParent(), "&&|%oror%|?|!")) continue; @@ -4633,7 +4633,7 @@ struct ConditionHandler { if (tok->hasKnownIntValue()) return; - Token* top = tok->astTop(); + Token* top = tok->astTop(true); if (Token::Match(top, "%assign%")) return; @@ -4852,7 +4852,7 @@ struct ConditionHandler { } } - Token* top = condTok->astTop(); + Token* top = condTok->astTop(true); if (top->previous()->isExpandedMacro()) { for (std::list* values : {&thenValues, &elseValues}) { @@ -5414,7 +5414,7 @@ static void valueFlowForLoopSimplify(Token* const bodyStart, } if (Token::Match(tok2, "%oror%|&&")) { - const ProgramMemory programMemory(getProgramMemory(tok2->astTop(), expr, ValueFlow::Value(value), settings)); + const ProgramMemory programMemory(getProgramMemory(tok2->astTop(true), expr, ValueFlow::Value(value), settings)); if ((tok2->str() == "&&" && !conditionIsTrue(tok2->astOperand1(), programMemory, settings)) || (tok2->str() == "||" && !conditionIsFalse(tok2->astOperand1(), programMemory, settings))) { // Skip second expression.. @@ -5439,11 +5439,11 @@ static void valueFlowForLoopSimplify(Token* const bodyStart, if ((tok2->str() == "&&" && conditionIsFalse(tok2->astOperand1(), - getProgramMemory(tok2->astTop(), expr, ValueFlow::Value(value), settings), + getProgramMemory(tok2->astTop(true), expr, ValueFlow::Value(value), settings), settings)) || (tok2->str() == "||" && conditionIsTrue(tok2->astOperand1(), - getProgramMemory(tok2->astTop(), expr, ValueFlow::Value(value), settings), + getProgramMemory(tok2->astTop(true), expr, ValueFlow::Value(value), settings), settings))) break; @@ -6799,7 +6799,7 @@ static void valueFlowContainerSize(const TokenList& tokenlist, !Token::Match(nameToken, "%name% (")) continue; } - if (Token::Match(nameToken->astTop()->previous(), "for|while")) + if (Token::Match(nameToken->astTop(true)->previous(), "for|while")) known = !isVariableChanged(var, settings); std::vector values{ValueFlow::Value{size}}; values.back().valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE; diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index b6abf31b424..74234cd56c7 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -6241,16 +6241,16 @@ class TestTokenizer : public TestFixture { // Return stringified AST if (style == AstStyle::Z3) - return tokenizer.list.front()->astTop()->astStringZ3(); + return tokenizer.list.front()->astTop(true)->astStringZ3(); std::string ret; std::set astTop; for (const Token *tok = tokenizer.list.front(); tok; tok = tok->next()) { - if (tok->astOperand1() && astTop.find(tok->astTop()) == astTop.end()) { - astTop.insert(tok->astTop()); + if (tok->astOperand1() && astTop.find(tok->astTop(true)) == astTop.end()) { + astTop.insert(tok->astTop(true)); if (!ret.empty()) ret += " "; - ret += tok->astTop()->astString(); + ret += tok->astTop(true)->astString(); } } return ret;