extracted `FwdAnalysis` from `astutils.{cpp|h}` (#4637)
* added missing filtering offiles in Visual Studio project * extracted `FwdAnalysis` from `astutils.{cpp|h}`
This commit is contained in:
parent
e2f398f81a
commit
29e0133cb5
8
Makefile
8
Makefile
|
@ -224,6 +224,7 @@ LIBOBJ = $(libcppdir)/analyzerinfo.o \
|
||||||
$(libcppdir)/errorlogger.o \
|
$(libcppdir)/errorlogger.o \
|
||||||
$(libcppdir)/errortypes.o \
|
$(libcppdir)/errortypes.o \
|
||||||
$(libcppdir)/forwardanalyzer.o \
|
$(libcppdir)/forwardanalyzer.o \
|
||||||
|
$(libcppdir)/fwdanalysis.o \
|
||||||
$(libcppdir)/importproject.o \
|
$(libcppdir)/importproject.o \
|
||||||
$(libcppdir)/infer.o \
|
$(libcppdir)/infer.o \
|
||||||
$(libcppdir)/library.o \
|
$(libcppdir)/library.o \
|
||||||
|
@ -500,7 +501,7 @@ $(libcppdir)/checkmemoryleak.o: lib/checkmemoryleak.cpp lib/astutils.h lib/check
|
||||||
$(libcppdir)/checknullpointer.o: lib/checknullpointer.cpp lib/astutils.h lib/check.h lib/checknullpointer.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
$(libcppdir)/checknullpointer.o: lib/checknullpointer.cpp lib/astutils.h lib/check.h lib/checknullpointer.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
||||||
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checknullpointer.cpp
|
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checknullpointer.cpp
|
||||||
|
|
||||||
$(libcppdir)/checkother.o: lib/checkother.cpp lib/astutils.h lib/check.h lib/checkclass.h lib/checkother.h lib/checkuninitvar.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
$(libcppdir)/checkother.o: lib/checkother.cpp lib/astutils.h lib/check.h lib/checkclass.h lib/checkother.h lib/checkuninitvar.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/fwdanalysis.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
||||||
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkother.cpp
|
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkother.cpp
|
||||||
|
|
||||||
$(libcppdir)/checkpostfixoperator.o: lib/checkpostfixoperator.cpp lib/check.h lib/checkpostfixoperator.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
$(libcppdir)/checkpostfixoperator.o: lib/checkpostfixoperator.cpp lib/check.h lib/checkpostfixoperator.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
||||||
|
@ -524,7 +525,7 @@ $(libcppdir)/checkuninitvar.o: lib/checkuninitvar.cpp lib/astutils.h lib/check.h
|
||||||
$(libcppdir)/checkunusedfunctions.o: lib/checkunusedfunctions.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/checkunusedfunctions.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
$(libcppdir)/checkunusedfunctions.o: lib/checkunusedfunctions.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/checkunusedfunctions.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
||||||
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedfunctions.cpp
|
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedfunctions.cpp
|
||||||
|
|
||||||
$(libcppdir)/checkunusedvar.o: lib/checkunusedvar.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/check.h lib/checkunusedvar.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
$(libcppdir)/checkunusedvar.o: lib/checkunusedvar.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/check.h lib/checkunusedvar.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
||||||
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedvar.cpp
|
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedvar.cpp
|
||||||
|
|
||||||
$(libcppdir)/checkvaarg.o: lib/checkvaarg.cpp lib/astutils.h lib/check.h lib/checkvaarg.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
$(libcppdir)/checkvaarg.o: lib/checkvaarg.cpp lib/astutils.h lib/check.h lib/checkvaarg.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
||||||
|
@ -551,6 +552,9 @@ $(libcppdir)/errortypes.o: lib/errortypes.cpp lib/config.h lib/errortypes.h
|
||||||
$(libcppdir)/forwardanalyzer.o: lib/forwardanalyzer.cpp lib/analyzer.h lib/astutils.h lib/config.h lib/errortypes.h lib/forwardanalyzer.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h
|
$(libcppdir)/forwardanalyzer.o: lib/forwardanalyzer.cpp lib/analyzer.h lib/astutils.h lib/config.h lib/errortypes.h lib/forwardanalyzer.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h
|
||||||
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/forwardanalyzer.cpp
|
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/forwardanalyzer.cpp
|
||||||
|
|
||||||
|
$(libcppdir)/fwdanalysis.o: lib/fwdanalysis.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/library.h lib/mathlib.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h
|
||||||
|
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/fwdanalysis.cpp
|
||||||
|
|
||||||
$(libcppdir)/importproject.o: lib/importproject.cpp externals/picojson/picojson.h externals/simplecpp/simplecpp.h externals/tinyxml2/tinyxml2.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
$(libcppdir)/importproject.o: lib/importproject.cpp externals/picojson/picojson.h externals/simplecpp/simplecpp.h externals/tinyxml2/tinyxml2.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
|
||||||
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/importproject.cpp
|
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/importproject.cpp
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,15 @@
|
||||||
<ClInclude Include="cppcheckexecutorsig.h">
|
<ClInclude Include="cppcheckexecutorsig.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="executor.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="filelister.h">
|
<ClInclude Include="filelister.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="processexecutor.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="stacktrace.h">
|
<ClInclude Include="stacktrace.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -55,12 +61,18 @@
|
||||||
<ClCompile Include="cppcheckexecutorsig.cpp">
|
<ClCompile Include="cppcheckexecutorsig.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="executor.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="filelister.cpp">
|
<ClCompile Include="filelister.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="cmdlineparser.cpp">
|
<ClCompile Include="cmdlineparser.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="processexecutor.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="stacktrace.cpp">
|
<ClCompile Include="stacktrace.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
529
lib/astutils.cpp
529
lib/astutils.cpp
|
@ -39,7 +39,6 @@
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
@ -3074,55 +3073,6 @@ const Token* findAllocFuncCallToken(const Token *expr, const Library &library)
|
||||||
return (Token::simpleMatch(expr, "new") && expr->astOperand1()) ? expr : nullptr;
|
return (Token::simpleMatch(expr, "new") && expr->astOperand1()) ? expr : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool nonLocal(const Variable* var, bool deref)
|
|
||||||
{
|
|
||||||
return !var || (!var->isLocal() && !var->isArgument()) || (deref && var->isArgument() && var->isPointer()) || var->isStatic() || var->isReference() || var->isExtern();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hasGccCompoundStatement(const Token *tok)
|
|
||||||
{
|
|
||||||
if (!tok)
|
|
||||||
return false;
|
|
||||||
if (tok->str() == "{" && Token::simpleMatch(tok->previous(), "( {"))
|
|
||||||
return true;
|
|
||||||
return hasGccCompoundStatement(tok->astOperand1()) || hasGccCompoundStatement(tok->astOperand2());
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hasFunctionCall(const Token *tok)
|
|
||||||
{
|
|
||||||
if (!tok)
|
|
||||||
return false;
|
|
||||||
if (Token::Match(tok, "%name% ("))
|
|
||||||
// todo, const/pure function?
|
|
||||||
return true;
|
|
||||||
return hasFunctionCall(tok->astOperand1()) || hasFunctionCall(tok->astOperand2());
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isUnchanged(const Token *startToken, const Token *endToken, const std::set<nonneg int> &exprVarIds, bool local)
|
|
||||||
{
|
|
||||||
for (const Token *tok = startToken; tok != endToken; tok = tok->next()) {
|
|
||||||
if (!local && Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {"))
|
|
||||||
// TODO: this is a quick bailout
|
|
||||||
return false;
|
|
||||||
if (tok->varId() == 0 || exprVarIds.find(tok->varId()) == exprVarIds.end())
|
|
||||||
continue;
|
|
||||||
const Token *parent = tok;
|
|
||||||
while (parent->astParent() && !parent->astParent()->isAssignmentOp() && parent->astParent()->tokType() != Token::Type::eIncDecOp) {
|
|
||||||
if (parent->str() == "," || parent->isUnaryOp("&"))
|
|
||||||
// TODO: This is a quick bailout
|
|
||||||
return false;
|
|
||||||
parent = parent->astParent();
|
|
||||||
}
|
|
||||||
if (parent->astParent()) {
|
|
||||||
if (parent->astParent()->tokType() == Token::Type::eIncDecOp)
|
|
||||||
return false;
|
|
||||||
else if (parent->astParent()->isAssignmentOp() && parent == parent->astParent()->astOperand1())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isNullOperand(const Token *expr)
|
bool isNullOperand(const Token *expr)
|
||||||
{
|
{
|
||||||
if (!expr)
|
if (!expr)
|
||||||
|
@ -3216,485 +3166,6 @@ bool isGlobalData(const Token *expr, bool cpp)
|
||||||
return globalData || !var;
|
return globalData || !var;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set<nonneg int> &exprVarIds, bool local, bool inInnerClass, int depth)
|
|
||||||
{
|
|
||||||
// Parse the given tokens
|
|
||||||
if (++depth > 1000)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
for (const Token* tok = startToken; precedes(tok, endToken); tok = tok->next()) {
|
|
||||||
if (Token::simpleMatch(tok, "try {")) {
|
|
||||||
// TODO: handle try
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Token::simpleMatch(tok, "break ;")) {
|
|
||||||
return Result(Result::Type::BREAK, tok);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Token::simpleMatch(tok, "goto"))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
if (!inInnerClass && tok->str() == "{" && tok->scope()->isClassOrStruct()) {
|
|
||||||
// skip returns from local class definition
|
|
||||||
FwdAnalysis::Result result = checkRecursive(expr, tok, tok->link(), exprVarIds, local, true, depth);
|
|
||||||
if (result.type != Result::Type::NONE)
|
|
||||||
return result;
|
|
||||||
tok=tok->link();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tok->str() == "continue")
|
|
||||||
// TODO
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
if (const Token *lambdaEndToken = findLambdaEndToken(tok)) {
|
|
||||||
tok = lambdaEndToken;
|
|
||||||
const Result lambdaResult = checkRecursive(expr, lambdaEndToken->link()->next(), lambdaEndToken, exprVarIds, local, inInnerClass, depth);
|
|
||||||
if (lambdaResult.type == Result::Type::READ || lambdaResult.type == Result::Type::BAILOUT)
|
|
||||||
return lambdaResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Token::Match(tok, "return|throw")) {
|
|
||||||
// TODO: Handle these better
|
|
||||||
// Is expr variable used in expression?
|
|
||||||
|
|
||||||
const Token* opTok = tok->astOperand1();
|
|
||||||
if (!opTok)
|
|
||||||
opTok = tok->next();
|
|
||||||
std::pair<const Token*, const Token*> startEndTokens = opTok->findExpressionStartEndTokens();
|
|
||||||
FwdAnalysis::Result result =
|
|
||||||
checkRecursive(expr, startEndTokens.first, startEndTokens.second->next(), exprVarIds, local, true, depth);
|
|
||||||
if (result.type != Result::Type::NONE)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
// #9167: if the return is inside an inner class, it does not tell us anything
|
|
||||||
if (!inInnerClass) {
|
|
||||||
if (!local && mWhat == What::Reassign)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
return Result(Result::Type::RETURN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tok->str() == "}") {
|
|
||||||
// Known value => possible value
|
|
||||||
if (tok->scope() == expr->scope())
|
|
||||||
mValueFlowKnown = false;
|
|
||||||
|
|
||||||
if (tok->scope()->isLoopScope()) {
|
|
||||||
// check condition
|
|
||||||
const Token *conditionStart = nullptr;
|
|
||||||
const Token *conditionEnd = nullptr;
|
|
||||||
if (Token::simpleMatch(tok->link()->previous(), ") {")) {
|
|
||||||
conditionEnd = tok->link()->previous();
|
|
||||||
conditionStart = conditionEnd->link();
|
|
||||||
} else if (Token::simpleMatch(tok->link()->previous(), "do {") && Token::simpleMatch(tok, "} while (")) {
|
|
||||||
conditionStart = tok->tokAt(2);
|
|
||||||
conditionEnd = conditionStart->link();
|
|
||||||
}
|
|
||||||
if (conditionStart && conditionEnd) {
|
|
||||||
bool used = false;
|
|
||||||
for (const Token *condTok = conditionStart; condTok != conditionEnd; condTok = condTok->next()) {
|
|
||||||
if (exprVarIds.find(condTok->varId()) != exprVarIds.end()) {
|
|
||||||
used = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (used)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check loop body again..
|
|
||||||
const struct FwdAnalysis::Result &result = checkRecursive(expr, tok->link(), tok, exprVarIds, local, inInnerClass, depth);
|
|
||||||
if (result.type == Result::Type::BAILOUT || result.type == Result::Type::READ)
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Token::simpleMatch(tok, "else {"))
|
|
||||||
tok = tok->linkAt(1);
|
|
||||||
|
|
||||||
if (Token::simpleMatch(tok, "asm ("))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
if (mWhat == What::ValueFlow && (Token::Match(tok, "while|for (") || Token::simpleMatch(tok, "do {"))) {
|
|
||||||
const Token *bodyStart = nullptr;
|
|
||||||
const Token *conditionStart = nullptr;
|
|
||||||
if (Token::simpleMatch(tok, "do {")) {
|
|
||||||
bodyStart = tok->next();
|
|
||||||
if (Token::simpleMatch(bodyStart->link(), "} while ("))
|
|
||||||
conditionStart = bodyStart->link()->tokAt(2);
|
|
||||||
} else {
|
|
||||||
conditionStart = tok->next();
|
|
||||||
if (Token::simpleMatch(conditionStart->link(), ") {"))
|
|
||||||
bodyStart = conditionStart->link()->next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bodyStart || !conditionStart)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
// Is expr changed in condition?
|
|
||||||
if (!isUnchanged(conditionStart, conditionStart->link(), exprVarIds, local))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
// Is expr changed in loop body?
|
|
||||||
if (!isUnchanged(bodyStart, bodyStart->link(), exprVarIds, local))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mWhat == What::ValueFlow && Token::simpleMatch(tok, "if (") && Token::simpleMatch(tok->linkAt(1), ") {")) {
|
|
||||||
const Token *bodyStart = tok->linkAt(1)->next();
|
|
||||||
const Token *conditionStart = tok->next();
|
|
||||||
const Token *condTok = conditionStart->astOperand2();
|
|
||||||
if (condTok->hasKnownIntValue()) {
|
|
||||||
const bool cond = condTok->values().front().intvalue;
|
|
||||||
if (cond) {
|
|
||||||
FwdAnalysis::Result result = checkRecursive(expr, bodyStart, bodyStart->link(), exprVarIds, local, true, depth);
|
|
||||||
if (result.type != Result::Type::NONE)
|
|
||||||
return result;
|
|
||||||
} else if (Token::simpleMatch(bodyStart->link(), "} else {")) {
|
|
||||||
bodyStart = bodyStart->link()->tokAt(2);
|
|
||||||
FwdAnalysis::Result result = checkRecursive(expr, bodyStart, bodyStart->link(), exprVarIds, local, true, depth);
|
|
||||||
if (result.type != Result::Type::NONE)
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tok = bodyStart->link();
|
|
||||||
if (isReturnScope(tok, &mLibrary))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
if (Token::simpleMatch(tok, "} else {"))
|
|
||||||
tok = tok->linkAt(2);
|
|
||||||
if (!tok)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
// Is expr changed in condition?
|
|
||||||
if (!isUnchanged(conditionStart, conditionStart->link(), exprVarIds, local))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
// Is expr changed in condition body?
|
|
||||||
if (!isUnchanged(bodyStart, bodyStart->link(), exprVarIds, local))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!local && Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {")) {
|
|
||||||
// TODO: this is a quick bailout
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mWhat == What::Reassign &&
|
|
||||||
Token::simpleMatch(tok, ";") &&
|
|
||||||
Token::simpleMatch(tok->astParent(), ";") &&
|
|
||||||
Token::simpleMatch(tok->astParent()->astParent(), "(") &&
|
|
||||||
Token::simpleMatch(tok->astParent()->astParent()->previous(), "for (") &&
|
|
||||||
!isUnchanged(tok, tok->astParent()->astParent()->link(), exprVarIds, local))
|
|
||||||
// TODO: This is a quick bailout to avoid FP #9420, there are false negatives (TODO_ASSERT_EQUALS)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
if (expr->isName() && Token::Match(tok, "%name% (") && tok->str().find("<") != std::string::npos && tok->str().find(expr->str()) != std::string::npos)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
if (exprVarIds.find(tok->varId()) != exprVarIds.end()) {
|
|
||||||
const Token *parent = tok;
|
|
||||||
bool other = false;
|
|
||||||
bool same = tok->astParent() && isSameExpression(mCpp, false, expr, tok, mLibrary, true, false, nullptr);
|
|
||||||
while (!same && Token::Match(parent->astParent(), "*|.|::|[|(|%cop%")) {
|
|
||||||
parent = parent->astParent();
|
|
||||||
if (parent->str() == "(" && !parent->isCast())
|
|
||||||
break;
|
|
||||||
if (isSameExpression(mCpp, false, expr, parent, mLibrary, true, false, nullptr)) {
|
|
||||||
same = true;
|
|
||||||
if (mWhat == What::ValueFlow) {
|
|
||||||
KnownAndToken v;
|
|
||||||
v.known = mValueFlowKnown;
|
|
||||||
v.token = parent;
|
|
||||||
mValueFlow.push_back(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Token::Match(parent, ". %var%") && parent->next()->varId() && exprVarIds.find(parent->next()->varId()) == exprVarIds.end() &&
|
|
||||||
isSameExpression(mCpp, false, expr->astOperand1(), parent->astOperand1(), mLibrary, true, false, nullptr)) {
|
|
||||||
other = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mWhat != What::ValueFlow && same && Token::simpleMatch(parent->astParent(), "[") && parent == parent->astParent()->astOperand2()) {
|
|
||||||
return Result(Result::Type::READ);
|
|
||||||
}
|
|
||||||
if (other)
|
|
||||||
continue;
|
|
||||||
if (Token::simpleMatch(parent->astParent(), "=") && parent == parent->astParent()->astOperand1()) {
|
|
||||||
if (!local && hasFunctionCall(parent->astParent()->astOperand2())) {
|
|
||||||
// TODO: this is a quick bailout
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
}
|
|
||||||
if (hasOperand(parent->astParent()->astOperand2(), expr)) {
|
|
||||||
if (mWhat == What::Reassign)
|
|
||||||
return Result(Result::Type::READ);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto startEnd = parent->astParent()->astOperand2()->findExpressionStartEndTokens();
|
|
||||||
for (const Token* tok2 = startEnd.first; tok2 != startEnd.second; tok2 = tok2->next()) {
|
|
||||||
if (tok2->tokType() == Token::eLambda)
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
// TODO: analyze usage in lambda
|
|
||||||
}
|
|
||||||
// ({ .. })
|
|
||||||
if (hasGccCompoundStatement(parent->astParent()->astOperand2()))
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
const bool reassign = isSameExpression(mCpp, false, expr, parent, mLibrary, false, false, nullptr);
|
|
||||||
if (reassign)
|
|
||||||
return Result(Result::Type::WRITE, parent->astParent());
|
|
||||||
return Result(Result::Type::READ);
|
|
||||||
} else if (mWhat == What::Reassign && parent->valueType() && parent->valueType()->pointer && Token::Match(parent->astParent(), "%assign%") && parent == parent->astParent()->astOperand1()) {
|
|
||||||
return Result(Result::Type::READ);
|
|
||||||
} else if (Token::Match(parent->astParent(), "%assign%") && !parent->astParent()->astParent() && parent == parent->astParent()->astOperand1()) {
|
|
||||||
if (mWhat == What::Reassign)
|
|
||||||
return Result(Result::Type::BAILOUT, parent->astParent());
|
|
||||||
if (mWhat == What::UnusedValue && (!parent->valueType() || parent->valueType()->reference != Reference::None))
|
|
||||||
return Result(Result::Type::BAILOUT, parent->astParent());
|
|
||||||
continue;
|
|
||||||
} else if (mWhat == What::UnusedValue && parent->isUnaryOp("&") && Token::Match(parent->astParent(), "[,(]")) {
|
|
||||||
// Pass variable to function the writes it
|
|
||||||
const Token *ftok = parent->astParent();
|
|
||||||
while (Token::simpleMatch(ftok, ","))
|
|
||||||
ftok = ftok->astParent();
|
|
||||||
if (ftok && Token::Match(ftok->previous(), "%name% (")) {
|
|
||||||
const std::vector<const Token *> args = getArguments(ftok);
|
|
||||||
int argnr = 0;
|
|
||||||
while (argnr < args.size() && args[argnr] != parent)
|
|
||||||
argnr++;
|
|
||||||
if (argnr < args.size()) {
|
|
||||||
const Library::Function* functionInfo = mLibrary.getFunction(ftok->astOperand1());
|
|
||||||
if (functionInfo) {
|
|
||||||
const auto it = functionInfo->argumentChecks.find(argnr + 1);
|
|
||||||
if (it != functionInfo->argumentChecks.end() && it->second.direction == Library::ArgumentChecks::Direction::DIR_OUT)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Result(Result::Type::BAILOUT, parent->astParent());
|
|
||||||
} else {
|
|
||||||
// TODO: this is a quick bailout
|
|
||||||
return Result(Result::Type::BAILOUT, parent->astParent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Token::Match(tok, ")|do {")) {
|
|
||||||
if (tok->str() == ")" && Token::simpleMatch(tok->link()->previous(), "switch ("))
|
|
||||||
// TODO: parse switch
|
|
||||||
return Result(Result::Type::BAILOUT);
|
|
||||||
const Result &result1 = checkRecursive(expr, tok->tokAt(2), tok->linkAt(1), exprVarIds, local, inInnerClass, depth);
|
|
||||||
if (result1.type == Result::Type::READ || result1.type == Result::Type::BAILOUT)
|
|
||||||
return result1;
|
|
||||||
if (mWhat == What::ValueFlow && result1.type == Result::Type::WRITE)
|
|
||||||
mValueFlowKnown = false;
|
|
||||||
if (mWhat == What::Reassign && result1.type == Result::Type::BREAK) {
|
|
||||||
const Token *scopeEndToken = findNextTokenFromBreak(result1.token);
|
|
||||||
if (scopeEndToken) {
|
|
||||||
const Result &result2 = checkRecursive(expr, scopeEndToken->next(), endToken, exprVarIds, local, inInnerClass, depth);
|
|
||||||
if (result2.type == Result::Type::BAILOUT)
|
|
||||||
return result2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Token::simpleMatch(tok->linkAt(1), "} else {")) {
|
|
||||||
const Token *elseStart = tok->linkAt(1)->tokAt(2);
|
|
||||||
const Result &result2 = checkRecursive(expr, elseStart, elseStart->link(), exprVarIds, local, inInnerClass, depth);
|
|
||||||
if (mWhat == What::ValueFlow && result2.type == Result::Type::WRITE)
|
|
||||||
mValueFlowKnown = false;
|
|
||||||
if (result2.type == Result::Type::READ || result2.type == Result::Type::BAILOUT)
|
|
||||||
return result2;
|
|
||||||
if (result1.type == Result::Type::WRITE && result2.type == Result::Type::WRITE)
|
|
||||||
return result1;
|
|
||||||
tok = elseStart->link();
|
|
||||||
} else {
|
|
||||||
tok = tok->linkAt(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result(Result::Type::NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hasVolatileCastOrVar(const Token *expr)
|
|
||||||
{
|
|
||||||
bool ret = false;
|
|
||||||
visitAstNodes(expr,
|
|
||||||
[&ret](const Token *tok) {
|
|
||||||
if (tok->variable() && tok->variable()->isVolatile())
|
|
||||||
ret = true;
|
|
||||||
else if (Token::simpleMatch(tok, "( volatile"))
|
|
||||||
ret = true;
|
|
||||||
return ret ? ChildrenToVisit::none : ChildrenToVisit::op1_and_op2;
|
|
||||||
});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FwdAnalysis::isGlobalData(const Token *expr) const
|
|
||||||
{
|
|
||||||
return ::isGlobalData(expr, mCpp);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<nonneg int> FwdAnalysis::getExprVarIds(const Token* expr, bool* localOut, bool* unknownVarIdOut) const
|
|
||||||
{
|
|
||||||
// all variable ids in expr.
|
|
||||||
std::set<nonneg int> exprVarIds;
|
|
||||||
bool local = true;
|
|
||||||
bool unknownVarId = false;
|
|
||||||
visitAstNodes(expr,
|
|
||||||
[&](const Token *tok) {
|
|
||||||
if (tok->str() == "[" && mWhat == What::UnusedValue)
|
|
||||||
return ChildrenToVisit::op1;
|
|
||||||
if (tok->varId() == 0 && tok->isName() && tok->previous()->str() != ".") {
|
|
||||||
// unknown variable
|
|
||||||
unknownVarId = true;
|
|
||||||
return ChildrenToVisit::none;
|
|
||||||
}
|
|
||||||
if (tok->varId() > 0) {
|
|
||||||
exprVarIds.insert(tok->varId());
|
|
||||||
if (!Token::simpleMatch(tok->previous(), ".")) {
|
|
||||||
const Variable *var = tok->variable();
|
|
||||||
if (var && var->isReference() && var->isLocal() && Token::Match(var->nameToken(), "%var% [=(]") && !isGlobalData(var->nameToken()->next()->astOperand2()))
|
|
||||||
return ChildrenToVisit::none;
|
|
||||||
const bool deref = tok->astParent() && (tok->astParent()->isUnaryOp("*") || (tok->astParent()->str() == "[" && tok == tok->astParent()->astOperand1()));
|
|
||||||
local &= !nonLocal(tok->variable(), deref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ChildrenToVisit::op1_and_op2;
|
|
||||||
});
|
|
||||||
if (localOut)
|
|
||||||
*localOut = local;
|
|
||||||
if (unknownVarIdOut)
|
|
||||||
*unknownVarIdOut = unknownVarId;
|
|
||||||
return exprVarIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
FwdAnalysis::Result FwdAnalysis::check(const Token* expr, const Token* startToken, const Token* endToken)
|
|
||||||
{
|
|
||||||
// all variable ids in expr.
|
|
||||||
bool local = true;
|
|
||||||
bool unknownVarId = false;
|
|
||||||
std::set<nonneg int> exprVarIds = getExprVarIds(expr, &local, &unknownVarId);
|
|
||||||
|
|
||||||
if (unknownVarId)
|
|
||||||
return Result(FwdAnalysis::Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
if (mWhat == What::Reassign && isGlobalData(expr))
|
|
||||||
local = false;
|
|
||||||
|
|
||||||
// In unused values checking we do not want to check assignments to
|
|
||||||
// global data.
|
|
||||||
if (mWhat == What::UnusedValue && isGlobalData(expr))
|
|
||||||
return Result(FwdAnalysis::Result::Type::BAILOUT);
|
|
||||||
|
|
||||||
Result result = checkRecursive(expr, startToken, endToken, exprVarIds, local, false);
|
|
||||||
|
|
||||||
// Break => continue checking in outer scope
|
|
||||||
while (mWhat!=What::ValueFlow && result.type == FwdAnalysis::Result::Type::BREAK) {
|
|
||||||
const Token *scopeEndToken = findNextTokenFromBreak(result.token);
|
|
||||||
if (!scopeEndToken)
|
|
||||||
break;
|
|
||||||
result = checkRecursive(expr, scopeEndToken->next(), endToken, exprVarIds, local, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FwdAnalysis::hasOperand(const Token *tok, const Token *lhs) const
|
|
||||||
{
|
|
||||||
if (!tok)
|
|
||||||
return false;
|
|
||||||
if (isSameExpression(mCpp, false, tok, lhs, mLibrary, false, false, nullptr))
|
|
||||||
return true;
|
|
||||||
return hasOperand(tok->astOperand1(), lhs) || hasOperand(tok->astOperand2(), lhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Token *FwdAnalysis::reassign(const Token *expr, const Token *startToken, const Token *endToken)
|
|
||||||
{
|
|
||||||
if (hasVolatileCastOrVar(expr))
|
|
||||||
return nullptr;
|
|
||||||
mWhat = What::Reassign;
|
|
||||||
Result result = check(expr, startToken, endToken);
|
|
||||||
return result.type == FwdAnalysis::Result::Type::WRITE ? result.token : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FwdAnalysis::unusedValue(const Token *expr, const Token *startToken, const Token *endToken)
|
|
||||||
{
|
|
||||||
if (isEscapedAlias(expr))
|
|
||||||
return false;
|
|
||||||
if (hasVolatileCastOrVar(expr))
|
|
||||||
return false;
|
|
||||||
mWhat = What::UnusedValue;
|
|
||||||
Result result = check(expr, startToken, endToken);
|
|
||||||
return (result.type == FwdAnalysis::Result::Type::NONE || result.type == FwdAnalysis::Result::Type::RETURN) && !possiblyAliased(expr, startToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FwdAnalysis::possiblyAliased(const Token *expr, const Token *startToken) const
|
|
||||||
{
|
|
||||||
if (expr->isUnaryOp("*"))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const bool macro = false;
|
|
||||||
const bool pure = false;
|
|
||||||
const bool followVar = false;
|
|
||||||
for (const Token *tok = startToken; tok; tok = tok->previous()) {
|
|
||||||
if (tok->str() == "{" && tok->scope()->type == Scope::eFunction && !(tok->astParent() && tok->astParent()->str() == ","))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (Token::Match(tok, "%name% (") && !Token::Match(tok, "if|while|for")) {
|
|
||||||
// Is argument passed by reference?
|
|
||||||
const std::vector<const Token*> args = getArguments(tok);
|
|
||||||
for (int argnr = 0; argnr < args.size(); ++argnr) {
|
|
||||||
if (!Token::Match(args[argnr], "%name%|.|::"))
|
|
||||||
continue;
|
|
||||||
if (tok->function() && tok->function()->getArgumentVar(argnr) && !tok->function()->getArgumentVar(argnr)->isReference() && !tok->function()->isConst())
|
|
||||||
continue;
|
|
||||||
for (const Token *subexpr = expr; subexpr; subexpr = subexpr->astOperand1()) {
|
|
||||||
if (isSameExpression(mCpp, macro, subexpr, args[argnr], mLibrary, pure, followVar)) {
|
|
||||||
const Scope* scope = expr->scope(); // if there is no other variable, assume no aliasing
|
|
||||||
if (scope->varlist.size() > 1)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Token *addrOf = nullptr;
|
|
||||||
if (Token::Match(tok, "& %name% ="))
|
|
||||||
addrOf = tok->tokAt(2)->astOperand2();
|
|
||||||
else if (tok->isUnaryOp("&"))
|
|
||||||
addrOf = tok->astOperand1();
|
|
||||||
else if (Token::simpleMatch(tok, "std :: ref ("))
|
|
||||||
addrOf = tok->tokAt(3)->astOperand2();
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (const Token *subexpr = expr; subexpr; subexpr = subexpr->astOperand1()) {
|
|
||||||
if (isSameExpression(mCpp, macro, subexpr, addrOf, mLibrary, pure, followVar))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FwdAnalysis::isEscapedAlias(const Token* expr)
|
|
||||||
{
|
|
||||||
for (const Token *subexpr = expr; subexpr; subexpr = subexpr->astOperand1()) {
|
|
||||||
for (const ValueFlow::Value &val : subexpr->values()) {
|
|
||||||
if (!val.isLocalLifetimeValue())
|
|
||||||
continue;
|
|
||||||
const Variable* var = val.tokvalue->variable();
|
|
||||||
if (!var)
|
|
||||||
continue;
|
|
||||||
if (!var->isLocal())
|
|
||||||
return true;
|
|
||||||
if (var->isArgument())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSizeOfEtc(const Token *tok)
|
bool isSizeOfEtc(const Token *tok)
|
||||||
{
|
{
|
||||||
return Token::Match(tok, "sizeof|typeof|offsetof|decltype|__typeof__ (");
|
return Token::Match(tok, "sizeof|typeof|offsetof|decltype|__typeof__ (");
|
||||||
|
|
|
@ -23,11 +23,9 @@
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <set>
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
@ -35,9 +33,9 @@
|
||||||
#include "library.h"
|
#include "library.h"
|
||||||
#include "smallvector.h"
|
#include "smallvector.h"
|
||||||
#include "symboldatabase.h"
|
#include "symboldatabase.h"
|
||||||
|
#include "token.h"
|
||||||
|
|
||||||
class Settings;
|
class Settings;
|
||||||
class Token;
|
|
||||||
|
|
||||||
enum class ChildrenToVisit {
|
enum class ChildrenToVisit {
|
||||||
none,
|
none,
|
||||||
|
@ -421,68 +419,6 @@ bool isScopeBracket(const Token* tok);
|
||||||
bool isNullOperand(const Token *expr);
|
bool isNullOperand(const Token *expr);
|
||||||
|
|
||||||
bool isGlobalData(const Token *expr, bool cpp);
|
bool isGlobalData(const Token *expr, bool cpp);
|
||||||
/**
|
|
||||||
* Forward data flow analysis for checks
|
|
||||||
* - unused value
|
|
||||||
* - redundant assignment
|
|
||||||
* - valueflow analysis
|
|
||||||
*/
|
|
||||||
class FwdAnalysis {
|
|
||||||
public:
|
|
||||||
FwdAnalysis(bool cpp, const Library &library) : mCpp(cpp), mLibrary(library), mWhat(What::Reassign), mValueFlowKnown(true) {}
|
|
||||||
|
|
||||||
bool hasOperand(const Token *tok, const Token *lhs) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if "expr" is reassigned. The "expr" can be a tree (x.y[12]).
|
|
||||||
* @param expr Symbolic expression to perform forward analysis for
|
|
||||||
* @param startToken First token in forward analysis
|
|
||||||
* @param endToken Last token in forward analysis
|
|
||||||
* @return Token where expr is reassigned. If it's not reassigned then nullptr is returned.
|
|
||||||
*/
|
|
||||||
const Token *reassign(const Token *expr, const Token *startToken, const Token *endToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if "expr" is used. The "expr" can be a tree (x.y[12]).
|
|
||||||
* @param expr Symbolic expression to perform forward analysis for
|
|
||||||
* @param startToken First token in forward analysis
|
|
||||||
* @param endToken Last token in forward analysis
|
|
||||||
* @return true if expr is used.
|
|
||||||
*/
|
|
||||||
bool unusedValue(const Token *expr, const Token *startToken, const Token *endToken);
|
|
||||||
|
|
||||||
struct KnownAndToken {
|
|
||||||
bool known;
|
|
||||||
const Token *token;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Is there some possible alias for given expression */
|
|
||||||
bool possiblyAliased(const Token *expr, const Token *startToken) const;
|
|
||||||
|
|
||||||
std::set<nonneg int> getExprVarIds(const Token* expr, bool* localOut = nullptr, bool* unknownVarIdOut = nullptr) const;
|
|
||||||
private:
|
|
||||||
static bool isEscapedAlias(const Token* expr);
|
|
||||||
|
|
||||||
/** Result of forward analysis */
|
|
||||||
struct Result {
|
|
||||||
enum class Type { NONE, READ, WRITE, BREAK, RETURN, BAILOUT } type;
|
|
||||||
explicit Result(Type type) : type(type), token(nullptr) {}
|
|
||||||
Result(Type type, const Token *token) : type(type), token(token) {}
|
|
||||||
const Token *token;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Result check(const Token *expr, const Token *startToken, const Token *endToken);
|
|
||||||
struct Result checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set<nonneg int> &exprVarIds, bool local, bool inInnerClass, int depth=0);
|
|
||||||
|
|
||||||
// Is expression a l-value global data?
|
|
||||||
bool isGlobalData(const Token *expr) const;
|
|
||||||
|
|
||||||
const bool mCpp;
|
|
||||||
const Library &mLibrary;
|
|
||||||
enum class What { Reassign, UnusedValue, ValueFlow } mWhat;
|
|
||||||
std::vector<KnownAndToken> mValueFlow;
|
|
||||||
bool mValueFlowKnown;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool isSizeOfEtc(const Token *tok);
|
bool isSizeOfEtc(const Token *tok);
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "checkother.h"
|
#include "checkother.h"
|
||||||
|
|
||||||
#include "astutils.h"
|
#include "astutils.h"
|
||||||
|
#include "fwdanalysis.h"
|
||||||
#include "library.h"
|
#include "library.h"
|
||||||
#include "mathlib.h"
|
#include "mathlib.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#include "astutils.h"
|
#include "astutils.h"
|
||||||
#include "errortypes.h"
|
#include "errortypes.h"
|
||||||
|
#include "fwdanalysis.h"
|
||||||
#include "library.h"
|
#include "library.h"
|
||||||
#include "preprocessor.h"
|
#include "preprocessor.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
<ClCompile Include="ctu.cpp" />
|
<ClCompile Include="ctu.cpp" />
|
||||||
<ClCompile Include="errorlogger.cpp" />
|
<ClCompile Include="errorlogger.cpp" />
|
||||||
<ClCompile Include="errortypes.cpp" />
|
<ClCompile Include="errortypes.cpp" />
|
||||||
|
<ClCompile Include="fwdanalysis.cpp" />
|
||||||
<ClCompile Include="infer.cpp" />
|
<ClCompile Include="infer.cpp" />
|
||||||
<ClCompile Include="library.cpp" />
|
<ClCompile Include="library.cpp" />
|
||||||
<ClCompile Include="mathlib.cpp" />
|
<ClCompile Include="mathlib.cpp" />
|
||||||
|
@ -143,6 +144,7 @@
|
||||||
<ClInclude Include="ctu.h" />
|
<ClInclude Include="ctu.h" />
|
||||||
<ClInclude Include="errorlogger.h" />
|
<ClInclude Include="errorlogger.h" />
|
||||||
<ClInclude Include="errortypes.h" />
|
<ClInclude Include="errortypes.h" />
|
||||||
|
<ClInclude Include="fwdanalysis.h" />
|
||||||
<ClInclude Include="forwardanalyzer.h" />
|
<ClInclude Include="forwardanalyzer.h" />
|
||||||
<ClInclude Include="infer.h" />
|
<ClInclude Include="infer.h" />
|
||||||
<ClInclude Include="library.h" />
|
<ClInclude Include="library.h" />
|
||||||
|
|
|
@ -62,6 +62,9 @@
|
||||||
<ClCompile Include="errortypes.cpp">
|
<ClCompile Include="errortypes.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="fwdanalysis.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="mathlib.cpp">
|
<ClCompile Include="mathlib.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -232,6 +235,9 @@
|
||||||
<ClInclude Include="errorlogger.h">
|
<ClInclude Include="errorlogger.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="fwdanalysis.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="mathlib.h">
|
<ClInclude Include="mathlib.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
|
@ -0,0 +1,556 @@
|
||||||
|
/*
|
||||||
|
* Cppcheck - A tool for static C/C++ code analysis
|
||||||
|
* Copyright (C) 2007-2021 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 "fwdanalysis.h"
|
||||||
|
|
||||||
|
#include "astutils.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "library.h"
|
||||||
|
#include "symboldatabase.h"
|
||||||
|
#include "token.h"
|
||||||
|
#include "valueflow.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static bool isUnchanged(const Token *startToken, const Token *endToken, const std::set<nonneg int> &exprVarIds, bool local)
|
||||||
|
{
|
||||||
|
for (const Token *tok = startToken; tok != endToken; tok = tok->next()) {
|
||||||
|
if (!local && Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {"))
|
||||||
|
// TODO: this is a quick bailout
|
||||||
|
return false;
|
||||||
|
if (tok->varId() == 0 || exprVarIds.find(tok->varId()) == exprVarIds.end())
|
||||||
|
continue;
|
||||||
|
const Token *parent = tok;
|
||||||
|
while (parent->astParent() && !parent->astParent()->isAssignmentOp() && parent->astParent()->tokType() != Token::Type::eIncDecOp) {
|
||||||
|
if (parent->str() == "," || parent->isUnaryOp("&"))
|
||||||
|
// TODO: This is a quick bailout
|
||||||
|
return false;
|
||||||
|
parent = parent->astParent();
|
||||||
|
}
|
||||||
|
if (parent->astParent()) {
|
||||||
|
if (parent->astParent()->tokType() == Token::Type::eIncDecOp)
|
||||||
|
return false;
|
||||||
|
else if (parent->astParent()->isAssignmentOp() && parent == parent->astParent()->astOperand1())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasFunctionCall(const Token *tok)
|
||||||
|
{
|
||||||
|
if (!tok)
|
||||||
|
return false;
|
||||||
|
if (Token::Match(tok, "%name% ("))
|
||||||
|
// todo, const/pure function?
|
||||||
|
return true;
|
||||||
|
return hasFunctionCall(tok->astOperand1()) || hasFunctionCall(tok->astOperand2());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasGccCompoundStatement(const Token *tok)
|
||||||
|
{
|
||||||
|
if (!tok)
|
||||||
|
return false;
|
||||||
|
if (tok->str() == "{" && Token::simpleMatch(tok->previous(), "( {"))
|
||||||
|
return true;
|
||||||
|
return hasGccCompoundStatement(tok->astOperand1()) || hasGccCompoundStatement(tok->astOperand2());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool nonLocal(const Variable* var, bool deref)
|
||||||
|
{
|
||||||
|
return !var || (!var->isLocal() && !var->isArgument()) || (deref && var->isArgument() && var->isPointer()) || var->isStatic() || var->isReference() || var->isExtern();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasVolatileCastOrVar(const Token *expr)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
visitAstNodes(expr,
|
||||||
|
[&ret](const Token *tok) {
|
||||||
|
if (tok->variable() && tok->variable()->isVolatile())
|
||||||
|
ret = true;
|
||||||
|
else if (Token::simpleMatch(tok, "( volatile"))
|
||||||
|
ret = true;
|
||||||
|
return ret ? ChildrenToVisit::none : ChildrenToVisit::op1_and_op2;
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set<nonneg int> &exprVarIds, bool local, bool inInnerClass, int depth)
|
||||||
|
{
|
||||||
|
// Parse the given tokens
|
||||||
|
if (++depth > 1000)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
for (const Token* tok = startToken; precedes(tok, endToken); tok = tok->next()) {
|
||||||
|
if (Token::simpleMatch(tok, "try {")) {
|
||||||
|
// TODO: handle try
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Token::simpleMatch(tok, "break ;")) {
|
||||||
|
return Result(Result::Type::BREAK, tok);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Token::simpleMatch(tok, "goto"))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
if (!inInnerClass && tok->str() == "{" && tok->scope()->isClassOrStruct()) {
|
||||||
|
// skip returns from local class definition
|
||||||
|
FwdAnalysis::Result result = checkRecursive(expr, tok, tok->link(), exprVarIds, local, true, depth);
|
||||||
|
if (result.type != Result::Type::NONE)
|
||||||
|
return result;
|
||||||
|
tok=tok->link();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok->str() == "continue")
|
||||||
|
// TODO
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
if (const Token *lambdaEndToken = findLambdaEndToken(tok)) {
|
||||||
|
tok = lambdaEndToken;
|
||||||
|
const Result lambdaResult = checkRecursive(expr, lambdaEndToken->link()->next(), lambdaEndToken, exprVarIds, local, inInnerClass, depth);
|
||||||
|
if (lambdaResult.type == Result::Type::READ || lambdaResult.type == Result::Type::BAILOUT)
|
||||||
|
return lambdaResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Token::Match(tok, "return|throw")) {
|
||||||
|
// TODO: Handle these better
|
||||||
|
// Is expr variable used in expression?
|
||||||
|
|
||||||
|
const Token* opTok = tok->astOperand1();
|
||||||
|
if (!opTok)
|
||||||
|
opTok = tok->next();
|
||||||
|
std::pair<const Token*, const Token*> startEndTokens = opTok->findExpressionStartEndTokens();
|
||||||
|
FwdAnalysis::Result result =
|
||||||
|
checkRecursive(expr, startEndTokens.first, startEndTokens.second->next(), exprVarIds, local, true, depth);
|
||||||
|
if (result.type != Result::Type::NONE)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// #9167: if the return is inside an inner class, it does not tell us anything
|
||||||
|
if (!inInnerClass) {
|
||||||
|
if (!local && mWhat == What::Reassign)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
return Result(Result::Type::RETURN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok->str() == "}") {
|
||||||
|
// Known value => possible value
|
||||||
|
if (tok->scope() == expr->scope())
|
||||||
|
mValueFlowKnown = false;
|
||||||
|
|
||||||
|
if (tok->scope()->isLoopScope()) {
|
||||||
|
// check condition
|
||||||
|
const Token *conditionStart = nullptr;
|
||||||
|
const Token *conditionEnd = nullptr;
|
||||||
|
if (Token::simpleMatch(tok->link()->previous(), ") {")) {
|
||||||
|
conditionEnd = tok->link()->previous();
|
||||||
|
conditionStart = conditionEnd->link();
|
||||||
|
} else if (Token::simpleMatch(tok->link()->previous(), "do {") && Token::simpleMatch(tok, "} while (")) {
|
||||||
|
conditionStart = tok->tokAt(2);
|
||||||
|
conditionEnd = conditionStart->link();
|
||||||
|
}
|
||||||
|
if (conditionStart && conditionEnd) {
|
||||||
|
bool used = false;
|
||||||
|
for (const Token *condTok = conditionStart; condTok != conditionEnd; condTok = condTok->next()) {
|
||||||
|
if (exprVarIds.find(condTok->varId()) != exprVarIds.end()) {
|
||||||
|
used = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (used)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check loop body again..
|
||||||
|
const struct FwdAnalysis::Result &result = checkRecursive(expr, tok->link(), tok, exprVarIds, local, inInnerClass, depth);
|
||||||
|
if (result.type == Result::Type::BAILOUT || result.type == Result::Type::READ)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Token::simpleMatch(tok, "else {"))
|
||||||
|
tok = tok->linkAt(1);
|
||||||
|
|
||||||
|
if (Token::simpleMatch(tok, "asm ("))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
if (mWhat == What::ValueFlow && (Token::Match(tok, "while|for (") || Token::simpleMatch(tok, "do {"))) {
|
||||||
|
const Token *bodyStart = nullptr;
|
||||||
|
const Token *conditionStart = nullptr;
|
||||||
|
if (Token::simpleMatch(tok, "do {")) {
|
||||||
|
bodyStart = tok->next();
|
||||||
|
if (Token::simpleMatch(bodyStart->link(), "} while ("))
|
||||||
|
conditionStart = bodyStart->link()->tokAt(2);
|
||||||
|
} else {
|
||||||
|
conditionStart = tok->next();
|
||||||
|
if (Token::simpleMatch(conditionStart->link(), ") {"))
|
||||||
|
bodyStart = conditionStart->link()->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bodyStart || !conditionStart)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
// Is expr changed in condition?
|
||||||
|
if (!isUnchanged(conditionStart, conditionStart->link(), exprVarIds, local))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
// Is expr changed in loop body?
|
||||||
|
if (!isUnchanged(bodyStart, bodyStart->link(), exprVarIds, local))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mWhat == What::ValueFlow && Token::simpleMatch(tok, "if (") && Token::simpleMatch(tok->linkAt(1), ") {")) {
|
||||||
|
const Token *bodyStart = tok->linkAt(1)->next();
|
||||||
|
const Token *conditionStart = tok->next();
|
||||||
|
const Token *condTok = conditionStart->astOperand2();
|
||||||
|
if (condTok->hasKnownIntValue()) {
|
||||||
|
const bool cond = condTok->values().front().intvalue;
|
||||||
|
if (cond) {
|
||||||
|
FwdAnalysis::Result result = checkRecursive(expr, bodyStart, bodyStart->link(), exprVarIds, local, true, depth);
|
||||||
|
if (result.type != Result::Type::NONE)
|
||||||
|
return result;
|
||||||
|
} else if (Token::simpleMatch(bodyStart->link(), "} else {")) {
|
||||||
|
bodyStart = bodyStart->link()->tokAt(2);
|
||||||
|
FwdAnalysis::Result result = checkRecursive(expr, bodyStart, bodyStart->link(), exprVarIds, local, true, depth);
|
||||||
|
if (result.type != Result::Type::NONE)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tok = bodyStart->link();
|
||||||
|
if (isReturnScope(tok, &mLibrary))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
if (Token::simpleMatch(tok, "} else {"))
|
||||||
|
tok = tok->linkAt(2);
|
||||||
|
if (!tok)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
// Is expr changed in condition?
|
||||||
|
if (!isUnchanged(conditionStart, conditionStart->link(), exprVarIds, local))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
// Is expr changed in condition body?
|
||||||
|
if (!isUnchanged(bodyStart, bodyStart->link(), exprVarIds, local))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!local && Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {")) {
|
||||||
|
// TODO: this is a quick bailout
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mWhat == What::Reassign &&
|
||||||
|
Token::simpleMatch(tok, ";") &&
|
||||||
|
Token::simpleMatch(tok->astParent(), ";") &&
|
||||||
|
Token::simpleMatch(tok->astParent()->astParent(), "(") &&
|
||||||
|
Token::simpleMatch(tok->astParent()->astParent()->previous(), "for (") &&
|
||||||
|
!isUnchanged(tok, tok->astParent()->astParent()->link(), exprVarIds, local))
|
||||||
|
// TODO: This is a quick bailout to avoid FP #9420, there are false negatives (TODO_ASSERT_EQUALS)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
if (expr->isName() && Token::Match(tok, "%name% (") && tok->str().find("<") != std::string::npos && tok->str().find(expr->str()) != std::string::npos)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
if (exprVarIds.find(tok->varId()) != exprVarIds.end()) {
|
||||||
|
const Token *parent = tok;
|
||||||
|
bool other = false;
|
||||||
|
bool same = tok->astParent() && isSameExpression(mCpp, false, expr, tok, mLibrary, true, false, nullptr);
|
||||||
|
while (!same && Token::Match(parent->astParent(), "*|.|::|[|(|%cop%")) {
|
||||||
|
parent = parent->astParent();
|
||||||
|
if (parent->str() == "(" && !parent->isCast())
|
||||||
|
break;
|
||||||
|
if (isSameExpression(mCpp, false, expr, parent, mLibrary, true, false, nullptr)) {
|
||||||
|
same = true;
|
||||||
|
if (mWhat == What::ValueFlow) {
|
||||||
|
KnownAndToken v;
|
||||||
|
v.known = mValueFlowKnown;
|
||||||
|
v.token = parent;
|
||||||
|
mValueFlow.push_back(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Token::Match(parent, ". %var%") && parent->next()->varId() && exprVarIds.find(parent->next()->varId()) == exprVarIds.end() &&
|
||||||
|
isSameExpression(mCpp, false, expr->astOperand1(), parent->astOperand1(), mLibrary, true, false, nullptr)) {
|
||||||
|
other = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mWhat != What::ValueFlow && same && Token::simpleMatch(parent->astParent(), "[") && parent == parent->astParent()->astOperand2()) {
|
||||||
|
return Result(Result::Type::READ);
|
||||||
|
}
|
||||||
|
if (other)
|
||||||
|
continue;
|
||||||
|
if (Token::simpleMatch(parent->astParent(), "=") && parent == parent->astParent()->astOperand1()) {
|
||||||
|
if (!local && hasFunctionCall(parent->astParent()->astOperand2())) {
|
||||||
|
// TODO: this is a quick bailout
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
}
|
||||||
|
if (hasOperand(parent->astParent()->astOperand2(), expr)) {
|
||||||
|
if (mWhat == What::Reassign)
|
||||||
|
return Result(Result::Type::READ);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto startEnd = parent->astParent()->astOperand2()->findExpressionStartEndTokens();
|
||||||
|
for (const Token* tok2 = startEnd.first; tok2 != startEnd.second; tok2 = tok2->next()) {
|
||||||
|
if (tok2->tokType() == Token::eLambda)
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
// TODO: analyze usage in lambda
|
||||||
|
}
|
||||||
|
// ({ .. })
|
||||||
|
if (hasGccCompoundStatement(parent->astParent()->astOperand2()))
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
const bool reassign = isSameExpression(mCpp, false, expr, parent, mLibrary, false, false, nullptr);
|
||||||
|
if (reassign)
|
||||||
|
return Result(Result::Type::WRITE, parent->astParent());
|
||||||
|
return Result(Result::Type::READ);
|
||||||
|
} else if (mWhat == What::Reassign && parent->valueType() && parent->valueType()->pointer && Token::Match(parent->astParent(), "%assign%") && parent == parent->astParent()->astOperand1()) {
|
||||||
|
return Result(Result::Type::READ);
|
||||||
|
} else if (Token::Match(parent->astParent(), "%assign%") && !parent->astParent()->astParent() && parent == parent->astParent()->astOperand1()) {
|
||||||
|
if (mWhat == What::Reassign)
|
||||||
|
return Result(Result::Type::BAILOUT, parent->astParent());
|
||||||
|
if (mWhat == What::UnusedValue && (!parent->valueType() || parent->valueType()->reference != Reference::None))
|
||||||
|
return Result(Result::Type::BAILOUT, parent->astParent());
|
||||||
|
continue;
|
||||||
|
} else if (mWhat == What::UnusedValue && parent->isUnaryOp("&") && Token::Match(parent->astParent(), "[,(]")) {
|
||||||
|
// Pass variable to function the writes it
|
||||||
|
const Token *ftok = parent->astParent();
|
||||||
|
while (Token::simpleMatch(ftok, ","))
|
||||||
|
ftok = ftok->astParent();
|
||||||
|
if (ftok && Token::Match(ftok->previous(), "%name% (")) {
|
||||||
|
const std::vector<const Token *> args = getArguments(ftok);
|
||||||
|
int argnr = 0;
|
||||||
|
while (argnr < args.size() && args[argnr] != parent)
|
||||||
|
argnr++;
|
||||||
|
if (argnr < args.size()) {
|
||||||
|
const Library::Function* functionInfo = mLibrary.getFunction(ftok->astOperand1());
|
||||||
|
if (functionInfo) {
|
||||||
|
const auto it = functionInfo->argumentChecks.find(argnr + 1);
|
||||||
|
if (it != functionInfo->argumentChecks.end() && it->second.direction == Library::ArgumentChecks::Direction::DIR_OUT)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result(Result::Type::BAILOUT, parent->astParent());
|
||||||
|
} else {
|
||||||
|
// TODO: this is a quick bailout
|
||||||
|
return Result(Result::Type::BAILOUT, parent->astParent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Token::Match(tok, ")|do {")) {
|
||||||
|
if (tok->str() == ")" && Token::simpleMatch(tok->link()->previous(), "switch ("))
|
||||||
|
// TODO: parse switch
|
||||||
|
return Result(Result::Type::BAILOUT);
|
||||||
|
const Result &result1 = checkRecursive(expr, tok->tokAt(2), tok->linkAt(1), exprVarIds, local, inInnerClass, depth);
|
||||||
|
if (result1.type == Result::Type::READ || result1.type == Result::Type::BAILOUT)
|
||||||
|
return result1;
|
||||||
|
if (mWhat == What::ValueFlow && result1.type == Result::Type::WRITE)
|
||||||
|
mValueFlowKnown = false;
|
||||||
|
if (mWhat == What::Reassign && result1.type == Result::Type::BREAK) {
|
||||||
|
const Token *scopeEndToken = findNextTokenFromBreak(result1.token);
|
||||||
|
if (scopeEndToken) {
|
||||||
|
const Result &result2 = checkRecursive(expr, scopeEndToken->next(), endToken, exprVarIds, local, inInnerClass, depth);
|
||||||
|
if (result2.type == Result::Type::BAILOUT)
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Token::simpleMatch(tok->linkAt(1), "} else {")) {
|
||||||
|
const Token *elseStart = tok->linkAt(1)->tokAt(2);
|
||||||
|
const Result &result2 = checkRecursive(expr, elseStart, elseStart->link(), exprVarIds, local, inInnerClass, depth);
|
||||||
|
if (mWhat == What::ValueFlow && result2.type == Result::Type::WRITE)
|
||||||
|
mValueFlowKnown = false;
|
||||||
|
if (result2.type == Result::Type::READ || result2.type == Result::Type::BAILOUT)
|
||||||
|
return result2;
|
||||||
|
if (result1.type == Result::Type::WRITE && result2.type == Result::Type::WRITE)
|
||||||
|
return result1;
|
||||||
|
tok = elseStart->link();
|
||||||
|
} else {
|
||||||
|
tok = tok->linkAt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(Result::Type::NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FwdAnalysis::isGlobalData(const Token *expr) const
|
||||||
|
{
|
||||||
|
return ::isGlobalData(expr, mCpp);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<nonneg int> FwdAnalysis::getExprVarIds(const Token* expr, bool* localOut, bool* unknownVarIdOut) const
|
||||||
|
{
|
||||||
|
// all variable ids in expr.
|
||||||
|
std::set<nonneg int> exprVarIds;
|
||||||
|
bool local = true;
|
||||||
|
bool unknownVarId = false;
|
||||||
|
visitAstNodes(expr,
|
||||||
|
[&](const Token *tok) {
|
||||||
|
if (tok->str() == "[" && mWhat == What::UnusedValue)
|
||||||
|
return ChildrenToVisit::op1;
|
||||||
|
if (tok->varId() == 0 && tok->isName() && tok->previous()->str() != ".") {
|
||||||
|
// unknown variable
|
||||||
|
unknownVarId = true;
|
||||||
|
return ChildrenToVisit::none;
|
||||||
|
}
|
||||||
|
if (tok->varId() > 0) {
|
||||||
|
exprVarIds.insert(tok->varId());
|
||||||
|
if (!Token::simpleMatch(tok->previous(), ".")) {
|
||||||
|
const Variable *var = tok->variable();
|
||||||
|
if (var && var->isReference() && var->isLocal() && Token::Match(var->nameToken(), "%var% [=(]") && !isGlobalData(var->nameToken()->next()->astOperand2()))
|
||||||
|
return ChildrenToVisit::none;
|
||||||
|
const bool deref = tok->astParent() && (tok->astParent()->isUnaryOp("*") || (tok->astParent()->str() == "[" && tok == tok->astParent()->astOperand1()));
|
||||||
|
local &= !nonLocal(tok->variable(), deref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ChildrenToVisit::op1_and_op2;
|
||||||
|
});
|
||||||
|
if (localOut)
|
||||||
|
*localOut = local;
|
||||||
|
if (unknownVarIdOut)
|
||||||
|
*unknownVarIdOut = unknownVarId;
|
||||||
|
return exprVarIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
FwdAnalysis::Result FwdAnalysis::check(const Token* expr, const Token* startToken, const Token* endToken)
|
||||||
|
{
|
||||||
|
// all variable ids in expr.
|
||||||
|
bool local = true;
|
||||||
|
bool unknownVarId = false;
|
||||||
|
std::set<nonneg int> exprVarIds = getExprVarIds(expr, &local, &unknownVarId);
|
||||||
|
|
||||||
|
if (unknownVarId)
|
||||||
|
return Result(FwdAnalysis::Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
if (mWhat == What::Reassign && isGlobalData(expr))
|
||||||
|
local = false;
|
||||||
|
|
||||||
|
// In unused values checking we do not want to check assignments to
|
||||||
|
// global data.
|
||||||
|
if (mWhat == What::UnusedValue && isGlobalData(expr))
|
||||||
|
return Result(FwdAnalysis::Result::Type::BAILOUT);
|
||||||
|
|
||||||
|
Result result = checkRecursive(expr, startToken, endToken, exprVarIds, local, false);
|
||||||
|
|
||||||
|
// Break => continue checking in outer scope
|
||||||
|
while (mWhat!=What::ValueFlow && result.type == FwdAnalysis::Result::Type::BREAK) {
|
||||||
|
const Token *scopeEndToken = findNextTokenFromBreak(result.token);
|
||||||
|
if (!scopeEndToken)
|
||||||
|
break;
|
||||||
|
result = checkRecursive(expr, scopeEndToken->next(), endToken, exprVarIds, local, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FwdAnalysis::hasOperand(const Token *tok, const Token *lhs) const
|
||||||
|
{
|
||||||
|
if (!tok)
|
||||||
|
return false;
|
||||||
|
if (isSameExpression(mCpp, false, tok, lhs, mLibrary, false, false, nullptr))
|
||||||
|
return true;
|
||||||
|
return hasOperand(tok->astOperand1(), lhs) || hasOperand(tok->astOperand2(), lhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Token *FwdAnalysis::reassign(const Token *expr, const Token *startToken, const Token *endToken)
|
||||||
|
{
|
||||||
|
if (hasVolatileCastOrVar(expr))
|
||||||
|
return nullptr;
|
||||||
|
mWhat = What::Reassign;
|
||||||
|
Result result = check(expr, startToken, endToken);
|
||||||
|
return result.type == FwdAnalysis::Result::Type::WRITE ? result.token : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FwdAnalysis::unusedValue(const Token *expr, const Token *startToken, const Token *endToken)
|
||||||
|
{
|
||||||
|
if (isEscapedAlias(expr))
|
||||||
|
return false;
|
||||||
|
if (hasVolatileCastOrVar(expr))
|
||||||
|
return false;
|
||||||
|
mWhat = What::UnusedValue;
|
||||||
|
Result result = check(expr, startToken, endToken);
|
||||||
|
return (result.type == FwdAnalysis::Result::Type::NONE || result.type == FwdAnalysis::Result::Type::RETURN) && !possiblyAliased(expr, startToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FwdAnalysis::possiblyAliased(const Token *expr, const Token *startToken) const
|
||||||
|
{
|
||||||
|
if (expr->isUnaryOp("*"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const bool macro = false;
|
||||||
|
const bool pure = false;
|
||||||
|
const bool followVar = false;
|
||||||
|
for (const Token *tok = startToken; tok; tok = tok->previous()) {
|
||||||
|
if (tok->str() == "{" && tok->scope()->type == Scope::eFunction && !(tok->astParent() && tok->astParent()->str() == ","))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (Token::Match(tok, "%name% (") && !Token::Match(tok, "if|while|for")) {
|
||||||
|
// Is argument passed by reference?
|
||||||
|
const std::vector<const Token*> args = getArguments(tok);
|
||||||
|
for (int argnr = 0; argnr < args.size(); ++argnr) {
|
||||||
|
if (!Token::Match(args[argnr], "%name%|.|::"))
|
||||||
|
continue;
|
||||||
|
if (tok->function() && tok->function()->getArgumentVar(argnr) && !tok->function()->getArgumentVar(argnr)->isReference() && !tok->function()->isConst())
|
||||||
|
continue;
|
||||||
|
for (const Token *subexpr = expr; subexpr; subexpr = subexpr->astOperand1()) {
|
||||||
|
if (isSameExpression(mCpp, macro, subexpr, args[argnr], mLibrary, pure, followVar)) {
|
||||||
|
const Scope* scope = expr->scope(); // if there is no other variable, assume no aliasing
|
||||||
|
if (scope->varlist.size() > 1)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Token *addrOf = nullptr;
|
||||||
|
if (Token::Match(tok, "& %name% ="))
|
||||||
|
addrOf = tok->tokAt(2)->astOperand2();
|
||||||
|
else if (tok->isUnaryOp("&"))
|
||||||
|
addrOf = tok->astOperand1();
|
||||||
|
else if (Token::simpleMatch(tok, "std :: ref ("))
|
||||||
|
addrOf = tok->tokAt(3)->astOperand2();
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const Token *subexpr = expr; subexpr; subexpr = subexpr->astOperand1()) {
|
||||||
|
if (isSameExpression(mCpp, macro, subexpr, addrOf, mLibrary, pure, followVar))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FwdAnalysis::isEscapedAlias(const Token* expr)
|
||||||
|
{
|
||||||
|
for (const Token *subexpr = expr; subexpr; subexpr = subexpr->astOperand1()) {
|
||||||
|
for (const ValueFlow::Value &val : subexpr->values()) {
|
||||||
|
if (!val.isLocalLifetimeValue())
|
||||||
|
continue;
|
||||||
|
const Variable* var = val.tokvalue->variable();
|
||||||
|
if (!var)
|
||||||
|
continue;
|
||||||
|
if (!var->isLocal())
|
||||||
|
return true;
|
||||||
|
if (var->isArgument())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Cppcheck - A tool for static C/C++ code analysis
|
||||||
|
* Copyright (C) 2007-2022 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#ifndef fwdanalysisH
|
||||||
|
#define fwdanalysisH
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Token;
|
||||||
|
class Library;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward data flow analysis for checks
|
||||||
|
* - unused value
|
||||||
|
* - redundant assignment
|
||||||
|
* - valueflow analysis
|
||||||
|
*/
|
||||||
|
class FwdAnalysis {
|
||||||
|
public:
|
||||||
|
FwdAnalysis(bool cpp, const Library &library) : mCpp(cpp), mLibrary(library), mWhat(What::Reassign), mValueFlowKnown(true) {}
|
||||||
|
|
||||||
|
bool hasOperand(const Token *tok, const Token *lhs) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if "expr" is reassigned. The "expr" can be a tree (x.y[12]).
|
||||||
|
* @param expr Symbolic expression to perform forward analysis for
|
||||||
|
* @param startToken First token in forward analysis
|
||||||
|
* @param endToken Last token in forward analysis
|
||||||
|
* @return Token where expr is reassigned. If it's not reassigned then nullptr is returned.
|
||||||
|
*/
|
||||||
|
const Token *reassign(const Token *expr, const Token *startToken, const Token *endToken);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if "expr" is used. The "expr" can be a tree (x.y[12]).
|
||||||
|
* @param expr Symbolic expression to perform forward analysis for
|
||||||
|
* @param startToken First token in forward analysis
|
||||||
|
* @param endToken Last token in forward analysis
|
||||||
|
* @return true if expr is used.
|
||||||
|
*/
|
||||||
|
bool unusedValue(const Token *expr, const Token *startToken, const Token *endToken);
|
||||||
|
|
||||||
|
struct KnownAndToken {
|
||||||
|
bool known;
|
||||||
|
const Token *token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Is there some possible alias for given expression */
|
||||||
|
bool possiblyAliased(const Token *expr, const Token *startToken) const;
|
||||||
|
|
||||||
|
std::set<nonneg int> getExprVarIds(const Token* expr, bool* localOut = nullptr, bool* unknownVarIdOut = nullptr) const;
|
||||||
|
private:
|
||||||
|
static bool isEscapedAlias(const Token* expr);
|
||||||
|
|
||||||
|
/** Result of forward analysis */
|
||||||
|
struct Result {
|
||||||
|
enum class Type { NONE, READ, WRITE, BREAK, RETURN, BAILOUT } type;
|
||||||
|
explicit Result(Type type) : type(type), token(nullptr) {}
|
||||||
|
Result(Type type, const Token *token) : type(type), token(token) {}
|
||||||
|
const Token *token;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Result check(const Token *expr, const Token *startToken, const Token *endToken);
|
||||||
|
struct Result checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set<nonneg int> &exprVarIds, bool local, bool inInnerClass, int depth=0);
|
||||||
|
|
||||||
|
// Is expression a l-value global data?
|
||||||
|
bool isGlobalData(const Token *expr) const;
|
||||||
|
|
||||||
|
const bool mCpp;
|
||||||
|
const Library &mLibrary;
|
||||||
|
enum class What { Reassign, UnusedValue, ValueFlow } mWhat;
|
||||||
|
std::vector<KnownAndToken> mValueFlow;
|
||||||
|
bool mValueFlowKnown;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // fwdanalysisH
|
|
@ -38,6 +38,7 @@ HEADERS += $${PWD}/analyzerinfo.h \
|
||||||
$${PWD}/errorlogger.h \
|
$${PWD}/errorlogger.h \
|
||||||
$${PWD}/errortypes.h \
|
$${PWD}/errortypes.h \
|
||||||
$${PWD}/forwardanalyzer.h \
|
$${PWD}/forwardanalyzer.h \
|
||||||
|
$${PWD}/fwdanalysis.h \
|
||||||
$${PWD}/importproject.h \
|
$${PWD}/importproject.h \
|
||||||
$${PWD}/infer.h \
|
$${PWD}/infer.h \
|
||||||
$${PWD}/library.h \
|
$${PWD}/library.h \
|
||||||
|
@ -96,6 +97,7 @@ SOURCES += $${PWD}/analyzerinfo.cpp \
|
||||||
$${PWD}/errorlogger.cpp \
|
$${PWD}/errorlogger.cpp \
|
||||||
$${PWD}/errortypes.cpp \
|
$${PWD}/errortypes.cpp \
|
||||||
$${PWD}/forwardanalyzer.cpp \
|
$${PWD}/forwardanalyzer.cpp \
|
||||||
|
$${PWD}/fwdanalysis.cpp \
|
||||||
$${PWD}/importproject.cpp \
|
$${PWD}/importproject.cpp \
|
||||||
$${PWD}/infer.cpp \
|
$${PWD}/infer.cpp \
|
||||||
$${PWD}/library.cpp \
|
$${PWD}/library.cpp \
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
<ClCompile Include="test64bit.cpp">
|
<ClCompile Include="test64bit.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="testanalyzerinformation.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="testautovariables.cpp">
|
<ClCompile Include="testautovariables.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -85,6 +88,9 @@
|
||||||
<ClCompile Include="testpreprocessor.cpp">
|
<ClCompile Include="testpreprocessor.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="testprocessexecutor.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="testrunner.cpp">
|
<ClCompile Include="testrunner.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
Loading…
Reference in New Issue