/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2025 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 .
*/
#include "forwardanalyzer.h"
#include "analyzer.h"
#include "astutils.h"
#include "config.h"
#include "errorlogger.h"
#include "errortypes.h"
#include "mathlib.h"
#include "settings.h"
#include "symboldatabase.h"
#include "token.h"
#include "tokenlist.h"
#include "utils.h"
#include "valueptr.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace {
struct ForwardTraversal {
enum class Progress : std::uint8_t { Continue, Break, Skip };
ForwardTraversal(const ValuePtr& analyzer, const TokenList& tokenList, ErrorLogger& errorLogger, const Settings& settings)
: analyzer(analyzer), tokenList(tokenList), errorLogger(errorLogger), settings(settings)
{}
ValuePtr analyzer;
const TokenList& tokenList;
ErrorLogger& errorLogger;
const Settings& settings;
Analyzer::Action actions;
bool analyzeOnly{};
bool analyzeTerminate{};
Analyzer::Terminate terminate = Analyzer::Terminate::None;
std::vector loopEnds;
int branchCount = 0;
// Nested condition-fork depth on this lineage (copied by fork()); bounds the fan-out.
int forkDepth = 0;
// Total forks of the traversal (shared via the fork() copy); backstop past the depth bound.
std::shared_ptr forkBudget = std::make_shared(0);
Progress Break(Analyzer::Terminate t = Analyzer::Terminate::None) {
if ((!analyzeOnly || analyzeTerminate) && t != Analyzer::Terminate::None)
terminate = t;
return Progress::Break;
}
struct Branch {
explicit Branch(Token* tok = nullptr) : endBlock(tok) {}
Token* endBlock = nullptr;
Analyzer::Action action = Analyzer::Action::None;
bool check = false;
bool escape = false;
bool escapeUnknown = false;
bool isEscape() const {
return escape || escapeUnknown;
}
bool isConclusiveEscape() const {
return escape && !escapeUnknown;
}
bool isModified() const {
return action.isModified() && !isConclusiveEscape();
}
bool isInconclusive() const {
return action.isInconclusive() && !isConclusiveEscape();
}
bool isDead() const {
return action.isModified() || action.isInconclusive() || isEscape();
}
bool hasGoto() const {
return endBlock ? ForwardTraversal::hasGoto(endBlock) : false;
}
};
bool stopUpdates() {
analyzeOnly = true;
return actions.isModified();
}
bool stopOnCondition(const Token* condTok) const
{
if (analyzer->isConditional() && findAstNode(condTok, [](const Token* tok) {
return tok->isIncompleteVar();
}))
return true;
return analyzer->stopOnCondition(condTok);
}
std::pair evalCond(const Token* tok, const Token* ctx = nullptr) const {
if (!tok)
return std::make_pair(false, false);
std::vector result = analyzer->evaluate(tok, ctx);
// TODO: We should convert to bool
const bool checkThen = std::any_of(result.cbegin(), result.cend(), [](MathLib::bigint x) {
return x != 0;
});
const bool checkElse = std::any_of(result.cbegin(), result.cend(), [](MathLib::bigint x) {
return x == 0;
});
return std::make_pair(checkThen, checkElse);
}
bool isConditionTrue(const Token* tok, const Token* ctx = nullptr) const {
return evalCond(tok, ctx).first;
}
template
Progress traverseTok(T* tok, const F &f, bool traverseUnknown, T** out = nullptr) {
if (Token::Match(tok, "asm|goto"))
return Break(Analyzer::Terminate::Bail);
if (Token::Match(tok, "setjmp|longjmp (")) {
// Traverse the parameters of the function before escaping
traverseRecursive(tok->next()->astOperand2(), f, traverseUnknown);
return Break(Analyzer::Terminate::Bail);
}
if (Token::simpleMatch(tok, "continue")) {
if (loopEnds.empty())
return Break(Analyzer::Terminate::Escape);
// If we are in a loop then jump to the end
if (out)
*out = loopEnds.back();
} else if (isEscapeKeyword(tok, settings)) {
traverseRecursive(tok->astOperand2(), f, traverseUnknown);
traverseRecursive(tok->astOperand1(), f, traverseUnknown);
return Break(Analyzer::Terminate::Escape);
} else if (Token::Match(tok, "%name% (") && isEscapeFunction(tok, settings.library)) {
// Traverse the parameters of the function before escaping
traverseRecursive(tok->next()->astOperand2(), f, traverseUnknown);
return Break(Analyzer::Terminate::Escape);
} else if (isUnevaluated(tok->previous())) {
if (out)
*out = tok->link();
return Progress::Skip;
} else if (tok->astOperand1() && tok->astOperand2() && Token::Match(tok, "?|&&|%oror%")) {
if (traverseConditional(tok, f, traverseUnknown) == Progress::Break)
return Break();
if (out)
*out = nextAfterAstRightmostLeaf(tok);
return Progress::Skip;
// Skip lambdas
} else if (T* lambdaEndToken = findLambdaEndToken(tok)) {
if (checkScope(lambdaEndToken).isModified())
return Break(Analyzer::Terminate::Bail);
if (out)
*out = lambdaEndToken->next();
// Skip class scope
} else if (tok->str() == "{" && tok->scope() && tok->scope()->isClassOrStruct()) {
if (out)
*out = tok->link();
} else {
if (f(tok) == Progress::Break)
return Break();
}
return Progress::Continue;
}
template
Progress traverseRecursive(T* tok, const F &f, bool traverseUnknown, unsigned int recursion=0) {
if (!tok)
return Progress::Continue;
if (recursion > 10000)
return Progress::Skip;
T* firstOp = tok->astOperand1();
T* secondOp = tok->astOperand2();
// Evaluate:
// 1. RHS of assignment before LHS
// 2. Unary op before operand
// 3. Function arguments before function call
if (tok->isAssignmentOp() || !secondOp || isFunctionCall(tok))
std::swap(firstOp, secondOp);
if (firstOp && traverseRecursive(firstOp, f, traverseUnknown, recursion+1) == Progress::Break)
return Break();
const Progress p = tok->isAssignmentOp() ? Progress::Continue : traverseTok(tok, f, traverseUnknown);
if (p == Progress::Break)
return Break();
if (p == Progress::Continue && secondOp && traverseRecursive(secondOp, f, traverseUnknown, recursion+1) == Progress::Break)
return Break();
if (tok->isAssignmentOp() && traverseTok(tok, f, traverseUnknown) == Progress::Break)
return Break();
return Progress::Continue;
}
template
Progress traverseConditional(T* tok, F f, bool traverseUnknown) {
Analyzer::Action action = analyzer->analyze(tok, Analyzer::Direction::Forward);
if (action.isNone() && Token::Match(tok, "?|&&|%oror%") && tok->astOperand1() && tok->astOperand2()) {
const T* condTok = tok->astOperand1();
T* childTok = tok->astOperand2();
bool checkThen, checkElse;
std::tie(checkThen, checkElse) = evalCond(condTok);
if (!checkThen && !checkElse) {
if (!traverseUnknown && stopOnCondition(condTok) && tok->str() != "?" && stopUpdates()) {
return Progress::Continue;
}
checkThen = true;
checkElse = true;
}
if (childTok->str() == ":") {
if (checkThen && traverseRecursive(childTok->astOperand1(), f, traverseUnknown) == Progress::Break)
return Break();
if (checkElse && traverseRecursive(childTok->astOperand2(), f, traverseUnknown) == Progress::Break)
return Break();
} else {
if (!checkThen && tok->str() == "&&")
return Progress::Continue;
if (!checkElse && tok->str() == "||")
return Progress::Continue;
if (traverseRecursive(childTok, f, traverseUnknown) == Progress::Break)
return Break();
}
} else {
return f(tok, action);
}
return Progress::Continue;
}
Progress update(Token* tok) {
Analyzer::Action action = analyzer->analyze(tok, Analyzer::Direction::Forward);
return update(tok, action);
}
Progress update(Token* tok, Analyzer::Action action)
{
actions |= action;
if (!action.isNone() && !analyzeOnly)
analyzer->update(tok, action, Analyzer::Direction::Forward);
if (action.isInconclusive() && !analyzer->lowerToInconclusive())
return Break(Analyzer::Terminate::Inconclusive);
if (action.isInvalid())
return Break(Analyzer::Terminate::Modified);
if (action.isWrite() && !action.isRead())
// Analysis of this write will continue separately
return Break(Analyzer::Terminate::Modified);
return Progress::Continue;
}
struct AsUpdate {
ForwardTraversal* self = nullptr;
explicit AsUpdate(ForwardTraversal* self) : self(self) {}
template
Progress operator()(Ts... xs) const
{
assert(self);
return self->update(xs ...);
}
};
Progress updateTok(Token* tok, Token** out = nullptr) {
return traverseTok(tok, AsUpdate{this}, false, out);
}
Progress updateRecursive(Token* tok) {
return traverseRecursive(tok, AsUpdate{this}, false);
}
struct AsAnalyze {
ForwardTraversal* self = nullptr;
Analyzer::Action* result = nullptr;
AsAnalyze(ForwardTraversal* self, Analyzer::Action* result) : self(self), result(result) {}
Progress operator()(const Token* tok) const
{
assert(self);
assert(result);
return (*this)(tok, self->analyzer->analyze(tok, Analyzer::Direction::Forward));
}
Progress operator()(const Token* /*unused*/, Analyzer::Action action) const
{
assert(self);
assert(result);
*result = action;
if (result->isModified() || result->isInconclusive())
return self->Break();
return Progress::Continue;
}
};
Analyzer::Action analyzeRecursive(const Token* start) {
Analyzer::Action result = Analyzer::Action::None;
traverseRecursive(start, AsAnalyze{this, &result}, true);
return result;
}
Analyzer::Action analyzeRange(const Token* start, const Token* end) const {
Analyzer::Action result = Analyzer::Action::None;
for (const Token* tok = start; tok && tok != end; tok = tok->next()) {
Analyzer::Action action = analyzer->analyze(tok, Analyzer::Direction::Forward);
if (action.isModified() || action.isInconclusive())
return action;
result |= action;
}
return result;
}
ForwardTraversal fork(bool analyze = false) const {
ForwardTraversal ft = *this;
if (analyze) {
ft.analyzeOnly = true;
ft.analyzeTerminate = true;
}
ft.actions = Analyzer::Action::None;
return ft;
}
std::vector tryForkScope(Token* endBlock, bool isModified = false) const {
if (analyzer->updateScope(endBlock, isModified)) {
ForwardTraversal ft = fork();
return {std::move(ft)};
}
return std::vector {};
}
std::vector tryForkUpdateScope(Token* endBlock, bool isModified = false) const {
std::vector result = tryForkScope(endBlock, isModified);
for (ForwardTraversal& ft : result)
ft.updateScope(endBlock);
return result;
}
static bool hasGoto(const Token* endBlock) {
return Token::findsimplematch(endBlock->link(), "goto", endBlock);
}
static bool hasJump(const Token* endBlock) {
return Token::findmatch(endBlock->link(), "goto|break", endBlock);
}
bool isEscapeScope(const Token* endBlock, bool& unknown) const {
const Token* ftok = nullptr;
const bool r = isReturnScope(endBlock, settings.library, &ftok);
if (!r && ftok)
unknown = true;
return r;
}
Analyzer::Action analyzeScope(const Token* endBlock) const {
return analyzeRange(endBlock->link(), endBlock);
}
Analyzer::Action checkScope(Token* endBlock) const {
Analyzer::Action a = analyzeScope(endBlock);
tryForkUpdateScope(endBlock, a.isModified());
return a;
}
Analyzer::Action checkScope(const Token* endBlock) const {
Analyzer::Action a = analyzeScope(endBlock);
return a;
}
Progress updateBranch(Branch& branch, int depth)
{
// Save and reset actions
Analyzer::Action prevActions = actions;
actions = Analyzer::Action::None;
Progress p = updateRange(branch.endBlock->link(), branch.endBlock, depth);
branch.action |= actions;
// Restore actions
actions |= prevActions;
if (terminate == Analyzer::Terminate::Escape) {
branch.escape = true;
// The traversal followed an escaping path, but if the scope does not structurally
// always escape then another path (e.g. a modified fork) falls through, so the escape
// is only conditional - keep isModified() meaningful by not treating it as conclusive.
bool structuralUnknown = false;
const bool structuralEscape = isEscapeScope(branch.endBlock, structuralUnknown);
branch.escapeUnknown = !structuralEscape || structuralUnknown;
// The traversal stopped at the escape, so the rest of the scope was not walked; a
// fall-through path could still modify the value there - include the whole scope's
// actions so isModified() sees it.
if (branch.escapeUnknown)
branch.action |= analyzeScope(branch.endBlock);
} else {
// Detect an escape the traversal did not flag (e.g. an unknown noreturn call);
// escapeUnknown reports a possible (unknown) escape.
branch.escape = isEscapeScope(branch.endBlock, branch.escapeUnknown);
if (terminate != Analyzer::Terminate::None && terminate != Analyzer::Terminate::Modified) {
branch.action |= analyzeScope(branch.endBlock);
}
}
return p;
}
// Update the branch that the evaluated condition takes
Progress updateTakenBranch(Branch& branch, const Token* skippedBlock, const Token* condTok, int depth)
{
// The condition is only "known" because of an earlier assumption, so the
// skipped block could still modify the value -> lower to possible
if (!condTok->hasKnownIntValue() && skippedBlock && analyzeScope(skippedBlock).isModified() &&
!analyzer->lowerToPossible())
return Break(Analyzer::Terminate::Bail);
if (!branch.endBlock)
return Progress::Continue;
updateScopeState(branch.endBlock);
if (updateBranch(branch, depth - 1) == Progress::Break)
return Progress::Break;
// The branch was entered because of the tracked value; if it might not
// return (it ends in a call to an unknown, possibly noreturn function)
// then the value might not flow past the branch.
if (!condTok->hasKnownIntValue() && !branch.escape && branch.escapeUnknown && !analyzer->lowerToInconclusive())
return Break(Analyzer::Terminate::Bail);
return Progress::Continue;
}
bool reentersLoop(Token* endBlock, const Token* condTok, const Token* stepTok) const {
if (!condTok)
return true;
if (Token::simpleMatch(condTok, ":"))
return true;
bool stepChangesCond = false;
if (stepTok) {
std::pair exprToks = stepTok->findExpressionStartEndTokens();
if (exprToks.first != nullptr && exprToks.second != nullptr)
stepChangesCond |=
findExpressionChanged(condTok, exprToks.first, exprToks.second->next(), settings) != nullptr;
}
const bool bodyChangesCond = findExpressionChanged(condTok, endBlock->link(), endBlock, settings);
// Check for mutation in the condition
const bool condChanged =
nullptr != findAstNode(condTok, [&](const Token* tok) {
return isVariableChanged(tok, 0, settings);
});
const bool changed = stepChangesCond || bodyChangesCond || condChanged;
if (!changed)
return true;
ForwardTraversal ft = fork(true);
ft.updateScope(endBlock);
return ft.isConditionTrue(condTok) && bodyChangesCond;
}
Progress updateInnerLoop(Token* endBlock, Token* stepTok, Token* condTok) {
loopEnds.push_back(endBlock);
OnExit oe{[&] {
loopEnds.pop_back();
}};
if (endBlock && updateScope(endBlock) == Progress::Break)
return Break();
if (stepTok && updateRecursive(stepTok) == Progress::Break)
return Break();
if (condTok && !Token::simpleMatch(condTok, ":") && updateRecursive(condTok) == Progress::Break)
return Break();
return Progress::Continue;
}
Progress updateLoop(const Token* endToken,
Token* endBlock,
Token* condTok,
Token* initTok = nullptr,
Token* stepTok = nullptr,
bool exit = false) {
if (initTok && updateRecursive(initTok) == Progress::Break)
return Break();
const bool isDoWhile = precedes(endBlock, condTok);
bool checkThen = true;
bool checkElse = false;
if (condTok && !Token::simpleMatch(condTok, ":"))
std::tie(checkThen, checkElse) = evalCond(condTok, isDoWhile ? endBlock->previous() : nullptr);
// exiting a do while(false)
if (checkElse && exit) {
if (hasJump(endBlock)) {
if (!analyzer->lowerToPossible())
return Break(Analyzer::Terminate::Bail);
if (analyzer->isConditional() && stopUpdates())
return Break(Analyzer::Terminate::Conditional);
}
return Progress::Continue;
}
Analyzer::Action bodyAnalysis = analyzeScope(endBlock);
Analyzer::Action allAnalysis = bodyAnalysis;
Analyzer::Action condAnalysis;
if (condTok) {
condAnalysis = analyzeRecursive(condTok);
allAnalysis |= condAnalysis;
}
if (stepTok)
allAnalysis |= analyzeRecursive(stepTok);
actions |= allAnalysis;
// do while(false) is not really a loop
if (checkElse && isDoWhile &&
(condTok->hasKnownIntValue() ||
(!bodyAnalysis.isModified() && !condAnalysis.isModified() && condAnalysis.isRead()))) {
if (updateScope(endBlock) == Progress::Break)
return Break();
return updateRecursive(condTok);
}
if (allAnalysis.isInconclusive()) {
if (!analyzer->lowerToInconclusive())
return Break(Analyzer::Terminate::Bail);
} else if (allAnalysis.isModified() || (exit && allAnalysis.isIdempotent())) {
if (!analyzer->lowerToPossible())
return Break(Analyzer::Terminate::Bail);
}
if (condTok && !Token::simpleMatch(condTok, ":")) {
if (!isDoWhile || (!bodyAnalysis.isModified() && !bodyAnalysis.isIdempotent()))
if (updateRecursive(condTok) == Progress::Break)
return Break();
}
if (!checkThen && !checkElse && !isDoWhile && stopOnCondition(condTok) && stopUpdates())
return Break(Analyzer::Terminate::Conditional);
// condition is false, we don't enter the loop
if (checkElse && !isDoWhile)
return Progress::Continue;
if (checkThen || isDoWhile) {
// Since we are re-entering the loop then assume the condition is true to update the state
if (exit)
analyzer->assume(condTok, true, Analyzer::Assume::Quiet | Analyzer::Assume::Absolute);
if (updateInnerLoop(endBlock, stepTok, condTok) == Progress::Break)
return Break();
// If loop re-enters then it could be modified again
if (allAnalysis.isModified() && reentersLoop(endBlock, condTok, stepTok))
return Break(Analyzer::Terminate::Bail);
if (allAnalysis.isIncremental())
return Break(Analyzer::Terminate::Bail);
} else if (allAnalysis.isModified()) {
std::vector ftv = tryForkScope(endBlock, allAnalysis.isModified());
bool forkContinue = true;
for (ForwardTraversal& ft : ftv) {
if (condTok)
ft.analyzer->assume(condTok, false, Analyzer::Assume::Quiet);
if (ft.updateInnerLoop(endBlock, stepTok, condTok) == Progress::Break)
forkContinue = false;
}
// TODO: Don't bail on missing condition
if (!condTok)
return Break(Analyzer::Terminate::Bail);
if (analyzer->isConditional() && stopUpdates())
return Break(Analyzer::Terminate::Conditional);
analyzer->assume(condTok, false);
if (forkContinue) {
for (ForwardTraversal& ft : ftv) {
if (!ft.actions.isIncremental())
ft.updateRange(endBlock, endToken);
}
}
if (allAnalysis.isIncremental())
return Break(Analyzer::Terminate::Bail);
} else {
if (updateInnerLoop(endBlock, stepTok, condTok) == Progress::Break)
return Progress::Break;
if (allAnalysis.isIncremental())
return Break(Analyzer::Terminate::Bail);
}
return Progress::Continue;
}
Progress updateLoopExit(const Token* endToken,
Token* endBlock,
Token* condTok,
Token* initTok = nullptr,
Token* stepTok = nullptr) {
return updateLoop(endToken, endBlock, condTok, initTok, stepTok, true);
}
void updateScopeState(const Token* endBlock)
{
assert(endBlock->link());
const Token* ctx = endBlock->link()->previous();
if (Token::simpleMatch(ctx, ")"))
ctx = ctx->link()->previous();
if (ctx)
analyzer->updateState(ctx);
}
Progress updateScope(Token* endBlock, int depth = 20)
{
if (!endBlock)
return Break();
updateScopeState(endBlock);
return updateRange(endBlock->link(), endBlock, depth);
}
/**
* @throws InternalError thrown on cyclic analysis
*/
Progress updateRange(Token* start, const Token* end, int depth = 20) {
if (depth < 0)
return Break(Analyzer::Terminate::Bail);
std::size_t i = 0;
for (Token* tok = start; precedes(tok, end); tok = tok->next()) {
Token* next = nullptr;
if (tok->index() index();
if (tok->link()) {
// Skip casts..
if (tok->str() == "(" && !tok->astOperand2() && tok->isCast()) {
tok = tok->link();
continue;
}
// Skip template arguments..
if (tok->str() == "");
}
static Token* assignExpr(Token* tok) {
while (tok->astParent() && astIsLHS(tok)) {
if (tok->astParent()->isAssignmentOp())
return tok->astParent();
tok = tok->astParent();
}
return nullptr;
}
static Token* callExpr(Token* tok)
{
while (tok->astParent() && astIsLHS(tok)) {
if (!Token::Match(tok, "%name%|::|