cppcheck/lib/pathanalysis.cpp

197 lines
7.7 KiB
C++

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2023 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 <http://www.gnu.org/licenses/>.
*/
#include "pathanalysis.h"
#include "astutils.h"
#include "symboldatabase.h"
#include "token.h"
#include "vfvalue.h"
#include <algorithm>
#include <memory>
#include <string>
#include <tuple>
#include <type_traits>
const Scope* PathAnalysis::findOuterScope(const Scope * scope)
{
if (!scope)
return nullptr;
if (scope->isLocal() && scope->type != Scope::eSwitch)
return findOuterScope(scope->nestedIn);
return scope;
}
static const Token* assignExpr(const Token* tok)
{
while (tok->astParent() && astIsLHS(tok)) {
if (Token::Match(tok->astParent(), "%assign%"))
return tok->astParent();
tok = tok->astParent();
}
return nullptr;
}
std::pair<bool, bool> PathAnalysis::checkCond(const Token * tok, bool& known)
{
if (tok->hasKnownIntValue()) {
known = true;
return std::make_pair(tok->values().front().intvalue, !tok->values().front().intvalue);
}
auto it = std::find_if(tok->values().cbegin(), tok->values().cend(), [](const ValueFlow::Value& v) {
return v.isIntValue();
});
// If all possible values are the same, then assume all paths have the same value
if (it != tok->values().cend() && std::all_of(it, tok->values().cend(), [&](const ValueFlow::Value& v) {
if (v.isIntValue())
return v.intvalue == it->intvalue;
return true;
})) {
known = false;
return std::make_pair(it->intvalue, !it->intvalue);
}
return std::make_pair(true, true);
}
PathAnalysis::Progress PathAnalysis::forwardRecursive(const Token* tok, Info info, const std::function<PathAnalysis::Progress(const Info&)>& f) const
{
if (!tok)
return Progress::Continue;
if (tok->astOperand1() && forwardRecursive(tok->astOperand1(), info, f) == Progress::Break)
return Progress::Break;
info.tok = tok;
if (f(info) == Progress::Break)
return Progress::Break;
if (tok->astOperand2() && forwardRecursive(tok->astOperand2(), info, f) == Progress::Break)
return Progress::Break;
return Progress::Continue;
}
PathAnalysis::Progress PathAnalysis::forwardRange(const Token* startToken, const Token* endToken, Info info, const std::function<PathAnalysis::Progress(const Info&)>& f) const
{
for (const Token *tok = startToken; precedes(tok, endToken); tok = tok->next()) {
if (Token::Match(tok, "asm|goto|break|continue"))
return Progress::Break;
else if (Token::Match(tok, "return|throw")) {
forwardRecursive(tok, info, f);
return Progress::Break;
// Evaluate RHS of assignment before LHS
} else if (const Token* assignTok = assignExpr(tok)) {
if (forwardRecursive(assignTok->astOperand2(), info, f) == Progress::Break)
return Progress::Break;
if (forwardRecursive(assignTok->astOperand1(), info, f) == Progress::Break)
return Progress::Break;
tok = nextAfterAstRightmostLeaf(assignTok);
if (!tok)
return Progress::Break;
} else if (Token::simpleMatch(tok, "}") && Token::simpleMatch(tok->link()->previous(), ") {") && Token::Match(tok->link()->linkAt(-1)->previous(), "if|while|for (")) {
const Token * blockStart = tok->link()->linkAt(-1)->previous();
const Token * condTok = getCondTok(blockStart);
if (!condTok)
continue;
info.errorPath.emplace_back(condTok, "Assuming condition is true.");
// Traverse a loop a second time
if (Token::Match(blockStart, "for|while (")) {
const Token* endCond = blockStart->linkAt(1);
bool traverseLoop = true;
// Only traverse simple for loops
if (Token::simpleMatch(blockStart, "for") && !Token::Match(endCond->tokAt(-3), "; ++|--|%var% %var%|++|-- ) {"))
traverseLoop = false;
// Traverse loop a second time
if (traverseLoop) {
// Traverse condition
if (forwardRecursive(condTok, info, f) == Progress::Break)
return Progress::Break;
// TODO: Should we traverse the body: forwardRange(tok->link(), tok, info, f)?
}
}
if (Token::simpleMatch(tok, "} else {")) {
tok = tok->linkAt(2);
}
} else if (Token::Match(tok, "if|while|for (") && Token::simpleMatch(tok->next()->link(), ") {")) {
const Token * endCond = tok->next()->link();
const Token * endBlock = endCond->next()->link();
const Token * condTok = getCondTok(tok);
if (!condTok)
continue;
// Traverse condition
if (forwardRange(tok->next(), tok->next()->link(), info, f) == Progress::Break)
return Progress::Break;
Info i = info;
i.known = false;
i.errorPath.emplace_back(condTok, "Assuming condition is true.");
// Check if condition is true or false
bool checkThen = false;
bool checkElse = false;
std::tie(checkThen, checkElse) = checkCond(condTok, i.known);
// Traverse then block
if (checkThen) {
if (forwardRange(endCond->next(), endBlock, i, f) == Progress::Break)
return Progress::Break;
}
// Traverse else block
if (Token::simpleMatch(endBlock, "} else {")) {
if (checkElse) {
i.errorPath.back().second = "Assuming condition is false.";
const Progress result = forwardRange(endCond->next(), endBlock, i, f);
if (result == Progress::Break)
return Progress::Break;
}
tok = endBlock->linkAt(2);
} else {
tok = endBlock;
}
} else if (Token::simpleMatch(tok, "} else {")) {
tok = tok->linkAt(2);
} else {
info.tok = tok;
if (f(info) == Progress::Break)
return Progress::Break;
}
// Prevent infinite recursion
if (tok->next() == start)
break;
}
return Progress::Continue;
}
void PathAnalysis::forward(const std::function<Progress(const Info&)>& f) const
{
const Scope * endScope = findOuterScope(start->scope());
if (!endScope)
return;
const Token * endToken = endScope->bodyEnd;
Info info{start, ErrorPath{}, true};
forwardRange(start, endToken, info, f);
}
bool reaches(const Token * start, const Token * dest, const Library& library, ErrorPath* errorPath)
{
PathAnalysis::Info info = PathAnalysis{start, library}.forwardFind([&](const PathAnalysis::Info& i) {
return (i.tok == dest);
});
if (!info.tok)
return false;
if (errorPath)
errorPath->insert(errorPath->end(), info.errorPath.cbegin(), info.errorPath.cend());
return true;
}