[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/cppcheck-opensource/cppcheck/main/lib/forwardanalyzer.cpp [Back]  [Original]

/*
 * 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%|::|

Web Proxy Viewer  |  New URL  |  Original Page