From 29e0133cb5a4b9ca7ec773ad351d6cd939a52fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 13 Dec 2022 22:29:23 +0100 Subject: [PATCH] extracted `FwdAnalysis` from `astutils.{cpp|h}` (#4637) * added missing filtering offiles in Visual Studio project * extracted `FwdAnalysis` from `astutils.{cpp|h}` --- Makefile | 8 +- cli/cli.vcxproj.filters | 12 + lib/astutils.cpp | 529 ------------------------------ lib/astutils.h | 66 +--- lib/checkother.cpp | 1 + lib/checkunusedvar.cpp | 1 + lib/cppcheck.vcxproj | 2 + lib/cppcheck.vcxproj.filters | 6 + lib/fwdanalysis.cpp | 556 ++++++++++++++++++++++++++++++++ lib/fwdanalysis.h | 95 ++++++ lib/lib.pri | 2 + test/testrunner.vcxproj.filters | 6 + 12 files changed, 688 insertions(+), 596 deletions(-) create mode 100644 lib/fwdanalysis.cpp create mode 100644 lib/fwdanalysis.h diff --git a/Makefile b/Makefile index be14a4c1c..c9d6e5d98 100644 --- a/Makefile +++ b/Makefile @@ -224,6 +224,7 @@ LIBOBJ = $(libcppdir)/analyzerinfo.o \ $(libcppdir)/errorlogger.o \ $(libcppdir)/errortypes.o \ $(libcppdir)/forwardanalyzer.o \ + $(libcppdir)/fwdanalysis.o \ $(libcppdir)/importproject.o \ $(libcppdir)/infer.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 $(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 $(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 $(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 $(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 $(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 $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/importproject.cpp diff --git a/cli/cli.vcxproj.filters b/cli/cli.vcxproj.filters index a378aeb51..3a1ce0ce7 100644 --- a/cli/cli.vcxproj.filters +++ b/cli/cli.vcxproj.filters @@ -32,9 +32,15 @@ Header Files + + Header Files + Header Files + + Header Files + Header Files @@ -55,12 +61,18 @@ Source Files + + Source Files + Source Files Source Files + + Source Files + Source Files diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 2c01884c9..9e3b136b0 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -3074,55 +3073,6 @@ const Token* findAllocFuncCallToken(const Token *expr, const Library &library) 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 &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) { if (!expr) @@ -3216,485 +3166,6 @@ bool isGlobalData(const Token *expr, bool cpp) return globalData || !var; } -struct FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set &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 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 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 FwdAnalysis::getExprVarIds(const Token* expr, bool* localOut, bool* unknownVarIdOut) const -{ - // all variable ids in expr. - std::set 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 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 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) { return Token::Match(tok, "sizeof|typeof|offsetof|decltype|__typeof__ ("); diff --git a/lib/astutils.h b/lib/astutils.h index 82ad052d2..d60ec3a5c 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -23,11 +23,9 @@ //--------------------------------------------------------------------------- #include -#include #include #include #include -#include #include #include "config.h" @@ -35,9 +33,9 @@ #include "library.h" #include "smallvector.h" #include "symboldatabase.h" +#include "token.h" class Settings; -class Token; enum class ChildrenToVisit { none, @@ -421,68 +419,6 @@ bool isScopeBracket(const Token* tok); bool isNullOperand(const Token *expr); 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 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 &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 mValueFlow; - bool mValueFlowKnown; -}; bool isSizeOfEtc(const Token *tok); diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 6baaf1721..f69f96260 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -21,6 +21,7 @@ #include "checkother.h" #include "astutils.h" +#include "fwdanalysis.h" #include "library.h" #include "mathlib.h" #include "settings.h" diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 77ea94a06..688b66683 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -22,6 +22,7 @@ #include "astutils.h" #include "errortypes.h" +#include "fwdanalysis.h" #include "library.h" #include "preprocessor.h" #include "settings.h" diff --git a/lib/cppcheck.vcxproj b/lib/cppcheck.vcxproj index 8f77ee9bb..46aca1004 100644 --- a/lib/cppcheck.vcxproj +++ b/lib/cppcheck.vcxproj @@ -79,6 +79,7 @@ + @@ -143,6 +144,7 @@ + diff --git a/lib/cppcheck.vcxproj.filters b/lib/cppcheck.vcxproj.filters index e3f777b75..5efd41acb 100644 --- a/lib/cppcheck.vcxproj.filters +++ b/lib/cppcheck.vcxproj.filters @@ -62,6 +62,9 @@ Source Files + + Source Files + Source Files @@ -232,6 +235,9 @@ Header Files + + Header Files + Header Files diff --git a/lib/fwdanalysis.cpp b/lib/fwdanalysis.cpp new file mode 100644 index 000000000..65ac89afd --- /dev/null +++ b/lib/fwdanalysis.cpp @@ -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 . + */ + +#include "fwdanalysis.h" + +#include "astutils.h" +#include "config.h" +#include "library.h" +#include "symboldatabase.h" +#include "token.h" +#include "valueflow.h" + +#include + +static bool isUnchanged(const Token *startToken, const Token *endToken, const std::set &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 &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 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 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 FwdAnalysis::getExprVarIds(const Token* expr, bool* localOut, bool* unknownVarIdOut) const +{ + // all variable ids in expr. + std::set 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 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 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; +} diff --git a/lib/fwdanalysis.h b/lib/fwdanalysis.h new file mode 100644 index 000000000..9c79f3548 --- /dev/null +++ b/lib/fwdanalysis.h @@ -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 . + */ + +//--------------------------------------------------------------------------- +#ifndef fwdanalysisH +#define fwdanalysisH +//--------------------------------------------------------------------------- + +#include "config.h" + +#include +#include + +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 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 &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 mValueFlow; + bool mValueFlowKnown; +}; + +#endif // fwdanalysisH diff --git a/lib/lib.pri b/lib/lib.pri index 694e3037f..6b81248b1 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -38,6 +38,7 @@ HEADERS += $${PWD}/analyzerinfo.h \ $${PWD}/errorlogger.h \ $${PWD}/errortypes.h \ $${PWD}/forwardanalyzer.h \ + $${PWD}/fwdanalysis.h \ $${PWD}/importproject.h \ $${PWD}/infer.h \ $${PWD}/library.h \ @@ -96,6 +97,7 @@ SOURCES += $${PWD}/analyzerinfo.cpp \ $${PWD}/errorlogger.cpp \ $${PWD}/errortypes.cpp \ $${PWD}/forwardanalyzer.cpp \ + $${PWD}/fwdanalysis.cpp \ $${PWD}/importproject.cpp \ $${PWD}/infer.cpp \ $${PWD}/library.cpp \ diff --git a/test/testrunner.vcxproj.filters b/test/testrunner.vcxproj.filters index 9fe2f07a4..7730fd4c8 100644 --- a/test/testrunner.vcxproj.filters +++ b/test/testrunner.vcxproj.filters @@ -25,6 +25,9 @@ Source Files + + Source Files + Source Files @@ -85,6 +88,9 @@ Source Files + + Source Files + Source Files