diff --git a/Makefile b/Makefile index 329f15d1c..93d74bad1 100644 --- a/Makefile +++ b/Makefile @@ -542,7 +542,7 @@ $(libcppdir)/platform.o: lib/platform.cpp externals/tinyxml2/tinyxml2.h lib/conf $(libcppdir)/preprocessor.o: lib/preprocessor.cpp externals/simplecpp/simplecpp.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/preprocessor.o $(libcppdir)/preprocessor.cpp -$(libcppdir)/programmemory.o: lib/programmemory.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/programmemory.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h +$(libcppdir)/programmemory.o: lib/programmemory.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/programmemory.h lib/settings.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 $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/programmemory.o $(libcppdir)/programmemory.cpp $(libcppdir)/reverseanalyzer.o: lib/reverseanalyzer.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/reverseanalyzer.h lib/settings.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 diff --git a/cfg/cppcheck-cfg.rng b/cfg/cppcheck-cfg.rng index 42977742b..b4a3878bc 100644 --- a/cfg/cppcheck-cfg.rng +++ b/cfg/cppcheck-cfg.rng @@ -109,6 +109,21 @@ + + + + + + + + + + + + + + + diff --git a/cfg/std.cfg b/cfg/std.cfg index 812c6e688..8f05e1822 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -7772,6 +7772,45 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init + + false + + + + + + + + + + false + + + + + + + + + + false + + + + + + + + + + false + + + + + + + diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 91ff2d724..27959e594 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -2065,7 +2066,7 @@ bool isVariableChanged(const Token *start, const Token *end, int indirect, const return findVariableChanged(start, end, indirect, exprid, globalvar, settings, cpp, depth) != nullptr; } -static const Token* findExpression(const Token* start, const nonneg int exprid) +const Token* findExpression(const Token* start, const nonneg int exprid) { Function * f = Scope::nestedInFunction(start->scope()); if (!f) @@ -2096,6 +2097,58 @@ static std::function memoize(F f) }; } +template()()), const Token*> )> +static bool isExpressionChangedAt(const F& getExprTok, + const Token* tok, + int indirect, + const nonneg int exprid, + bool globalvar, + const Settings* settings, + bool cpp, + int depth) +{ + if (tok->exprId() != exprid) { + if (globalvar && Token::Match(tok, "%name% (")) + // TODO: Is global variable really changed by function call? + return true; + // Is aliased function call or alias passed to function + if ((Token::Match(tok, "%var% (") || isVariableChangedByFunctionCall(tok, 1, settings)) && + std::any_of(tok->values().begin(), tok->values().end(), std::mem_fn(&ValueFlow::Value::isLifetimeValue))) { + bool aliased = false; + // If we can't find the expression then assume it was modified + if (!getExprTok()) + return true; + visitAstNodes(getExprTok(), [&](const Token* childTok) { + if (childTok->varId() > 0 && isAliasOf(tok, childTok->varId())) { + aliased = true; + return ChildrenToVisit::done; + } + return ChildrenToVisit::op1_and_op2; + }); + // TODO: Try to traverse the lambda function + if (aliased) + return true; + } + return false; + } + return (isVariableChanged(tok, indirect, settings, cpp, depth)); +} + +bool isExpressionChangedAt(const Token* expr, + const Token* tok, + int indirect, + bool globalvar, + const Settings* settings, + bool cpp, + int depth) +{ + return isExpressionChangedAt([&] { + return expr; + }, tok, indirect, expr->exprId(), globalvar, settings, cpp, depth); +} + Token* findVariableChanged(Token *start, const Token *end, int indirect, const nonneg int exprid, bool globalvar, const Settings *settings, bool cpp, int depth) { if (!precedes(start, end)) @@ -2106,31 +2159,7 @@ Token* findVariableChanged(Token *start, const Token *end, int indirect, const n return findExpression(start, exprid); }); for (Token *tok = start; tok != end; tok = tok->next()) { - if (tok->exprId() != exprid) { - if (globalvar && Token::Match(tok, "%name% (")) - // TODO: Is global variable really changed by function call? - return tok; - // Is aliased function call or alias passed to function - if ((Token::Match(tok, "%var% (") || isVariableChangedByFunctionCall(tok, 1, settings)) && - std::any_of(tok->values().begin(), tok->values().end(), std::mem_fn(&ValueFlow::Value::isLifetimeValue))) { - bool aliased = false; - // If we can't find the expression then assume it was modified - if (!getExprTok()) - return tok; - visitAstNodes(getExprTok(), [&](const Token* childTok) { - if (childTok->varId() > 0 && isAliasOf(tok, childTok->varId())) { - aliased = true; - return ChildrenToVisit::done; - } - return ChildrenToVisit::op1_and_op2; - }); - // TODO: Try to traverse the lambda function - if (aliased) - return tok; - } - continue; - } - if (isVariableChanged(tok, indirect, settings, cpp, depth)) + if (isExpressionChangedAt(getExprTok, tok, indirect, exprid, globalvar, settings, cpp, depth)) return tok; } return nullptr; @@ -2223,10 +2252,14 @@ bool isExpressionChanged(const Token* expr, const Token* start, const Token* end return false; global = !tok->variable()->isLocal() && !tok->variable()->isArgument(); } - if (tok->exprId() > 0 && - isVariableChanged( - start, end, tok->valueType() ? tok->valueType()->pointer : 0, tok->exprId(), global, settings, cpp, depth)) - return true; + + if (tok->exprId() > 0) { + for (const Token* tok2 = start; tok2 != end; tok2 = tok2->next()) { + if (isExpressionChangedAt( + tok, tok2, tok->valueType() ? tok->valueType()->pointer : 0, global, settings, cpp, depth)) + return true; + } + } return false; }); return result; diff --git a/lib/astutils.h b/lib/astutils.h index f73017aad..e7a34aa64 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -55,6 +55,7 @@ const Token* findExpression(const nonneg int exprid, const Token* start, const Token* end, const std::function& pred); +const Token* findExpression(const Token* start, const nonneg int exprid); std::vector astFlatten(const Token* tok, const char* op); @@ -241,6 +242,14 @@ bool isExpressionChanged(const Token* expr, bool cpp, int depth = 20); +bool isExpressionChangedAt(const Token* expr, + const Token* tok, + int indirect, + bool globalvar, + const Settings* settings, + bool cpp, + int depth = 20); + /// If token is an alias if another variable bool isAliasOf(const Token *tok, nonneg int varid, bool* inconclusive = nullptr); diff --git a/lib/library.cpp b/lib/library.cpp index 14fe29e6c..827a8c6a8 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -135,6 +135,55 @@ Library::Error Library::load(const char exename[], const char path[]) } } +Library::Container::Yield Library::Container::yieldFrom(const std::string& yieldName) +{ + if (yieldName == "at_index") + return Container::Yield::AT_INDEX; + else if (yieldName == "item") + return Container::Yield::ITEM; + else if (yieldName == "buffer") + return Container::Yield::BUFFER; + else if (yieldName == "buffer-nt") + return Container::Yield::BUFFER_NT; + else if (yieldName == "start-iterator") + return Container::Yield::START_ITERATOR; + else if (yieldName == "end-iterator") + return Container::Yield::END_ITERATOR; + else if (yieldName == "iterator") + return Container::Yield::ITERATOR; + else if (yieldName == "size") + return Container::Yield::SIZE; + else if (yieldName == "empty") + return Container::Yield::EMPTY; + else + return Container::Yield::NO_YIELD; +} +Library::Container::Action Library::Container::actionFrom(const std::string& actionName) +{ + if (actionName == "resize") + return Container::Action::RESIZE; + else if (actionName == "clear") + return Container::Action::CLEAR; + else if (actionName == "push") + return Container::Action::PUSH; + else if (actionName == "pop") + return Container::Action::POP; + else if (actionName == "find") + return Container::Action::FIND; + else if (actionName == "insert") + return Container::Action::INSERT; + else if (actionName == "erase") + return Container::Action::ERASE; + else if (actionName == "change-content") + return Container::Action::CHANGE_CONTENT; + else if (actionName == "change-internal") + return Container::Action::CHANGE_INTERNAL; + else if (actionName == "change") + return Container::Action::CHANGE; + else + return Container::Action::NO_ACTION; +} + bool Library::loadxmldata(const char xmldata[], std::size_t len) { tinyxml2::XMLDocument doc; @@ -408,27 +457,8 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc) Container::Action action = Container::Action::NO_ACTION; if (action_ptr) { std::string actionName = action_ptr; - if (actionName == "resize") - action = Container::Action::RESIZE; - else if (actionName == "clear") - action = Container::Action::CLEAR; - else if (actionName == "push") - action = Container::Action::PUSH; - else if (actionName == "pop") - action = Container::Action::POP; - else if (actionName == "find") - action = Container::Action::FIND; - else if (actionName == "insert") - action = Container::Action::INSERT; - else if (actionName == "erase") - action = Container::Action::ERASE; - else if (actionName == "change-content") - action = Container::Action::CHANGE_CONTENT; - else if (actionName == "change-internal") - action = Container::Action::CHANGE_INTERNAL; - else if (actionName == "change") - action = Container::Action::CHANGE; - else + action = Container::actionFrom(actionName); + if (action == Container::Action::NO_ACTION) return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, actionName); } @@ -436,25 +466,8 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc) Container::Yield yield = Container::Yield::NO_YIELD; if (yield_ptr) { std::string yieldName = yield_ptr; - if (yieldName == "at_index") - yield = Container::Yield::AT_INDEX; - else if (yieldName == "item") - yield = Container::Yield::ITEM; - else if (yieldName == "buffer") - yield = Container::Yield::BUFFER; - else if (yieldName == "buffer-nt") - yield = Container::Yield::BUFFER_NT; - else if (yieldName == "start-iterator") - yield = Container::Yield::START_ITERATOR; - else if (yieldName == "end-iterator") - yield = Container::Yield::END_ITERATOR; - else if (yieldName == "iterator") - yield = Container::Yield::ITERATOR; - else if (yieldName == "size") - yield = Container::Yield::SIZE; - else if (yieldName == "empty") - yield = Container::Yield::EMPTY; - else + yield = Container::yieldFrom(yieldName); + if (yield == Container::Yield::NO_YIELD) return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, yieldName); } @@ -835,6 +848,26 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co } functionwarn[name] = wi; + } else if (functionnodename == "container") { + const char* const action_ptr = functionnode->Attribute("action"); + Container::Action action = Container::Action::NO_ACTION; + if (action_ptr) { + std::string actionName = action_ptr; + action = Container::actionFrom(actionName); + if (action == Container::Action::NO_ACTION) + return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, actionName); + } + func.containerAction = action; + + const char* const yield_ptr = functionnode->Attribute("yields"); + Container::Yield yield = Container::Yield::NO_YIELD; + if (yield_ptr) { + std::string yieldName = yield_ptr; + yield = Container::yieldFrom(yieldName); + if (yield == Container::Yield::NO_YIELD) + return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, yieldName); + } + func.containerYield = yield; } else unknown_elements.insert(functionnodename); } @@ -1508,6 +1541,53 @@ bool Library::isimporter(const std::string& file, const std::string &importer) c return (it != mImporters.end() && it->second.count(importer) > 0); } +const Token* Library::getContainerFromYield(const Token* tok, Library::Container::Yield yield) const +{ + if (!tok) + return nullptr; + if (Token::Match(tok->tokAt(-2), ". %name% (")) { + const Token* containerTok = tok->tokAt(-2)->astOperand1(); + if (!astIsContainer(containerTok)) + return nullptr; + if (containerTok->valueType()->container && + containerTok->valueType()->container->getYield(tok->strAt(-1)) == yield) + return containerTok; + if (yield == Library::Container::Yield::EMPTY && Token::simpleMatch(tok->tokAt(-1), "empty ( )")) + return containerTok; + if (yield == Library::Container::Yield::SIZE && Token::Match(tok->tokAt(-1), "size|length ( )")) + return containerTok; + } else if (Token::Match(tok->previous(), "%name% (")) { + if (const Library::Function* f = this->getFunction(tok->previous())) { + if (f->containerYield == yield) { + return tok->astOperand2(); + } + } + } + return nullptr; +} +const Token* Library::getContainerFromAction(const Token* tok, Library::Container::Action action) const +{ + if (!tok) + return nullptr; + if (Token::Match(tok->tokAt(-2), ". %name% (")) { + const Token* containerTok = tok->tokAt(-2)->astOperand1(); + if (!astIsContainer(containerTok)) + return nullptr; + if (containerTok->valueType()->container && + containerTok->valueType()->container->getAction(tok->strAt(-1)) == action) + return containerTok; + if (Token::simpleMatch(tok->tokAt(-1), "empty ( )")) + return containerTok; + } else if (Token::Match(tok->previous(), "%name% (")) { + if (const Library::Function* f = this->getFunction(tok->previous())) { + if (f->containerAction == action) { + return tok->astOperand2(); + } + } + } + return nullptr; +} + bool Library::isSmartPointer(const Token* tok) const { return detectSmartPointer(tok); diff --git a/lib/library.h b/lib/library.h index 0c67395d5..e929f6e3e 100644 --- a/lib/library.h +++ b/lib/library.h @@ -252,6 +252,9 @@ public: return i->second.yield; return Yield::NO_YIELD; } + + static Yield yieldFrom(const std::string& yieldName); + static Action actionFrom(const std::string& actionName); }; std::map containers; const Container* detectContainer(const Token* typeStart, bool iterator = false) const; @@ -320,7 +323,21 @@ public: bool formatstr; bool formatstr_scan; bool formatstr_secure; - Function() : use(false), leakignore(false), isconst(false), ispure(false), useretval(UseRetValType::NONE), ignore(false), formatstr(false), formatstr_scan(false), formatstr_secure(false) {} + Container::Action containerAction; + Container::Yield containerYield; + Function() + : use(false), + leakignore(false), + isconst(false), + ispure(false), + useretval(UseRetValType::NONE), + ignore(false), + formatstr(false), + formatstr_scan(false), + formatstr_secure(false), + containerAction(Container::Action::NO_ACTION), + containerYield(Container::Yield::NO_YIELD) + {} }; const Function *getFunction(const Token *ftok) const; @@ -417,6 +434,9 @@ public: bool isimporter(const std::string& file, const std::string &importer) const; + const Token* getContainerFromYield(const Token* tok, Container::Yield yield) const; + const Token* getContainerFromAction(const Token* tok, Container::Action action) const; + bool isreflection(const std::string &token) const { return mReflection.find(token) != mReflection.end(); } diff --git a/lib/programmemory.cpp b/lib/programmemory.cpp index 821a43636..b95a222c0 100644 --- a/lib/programmemory.cpp +++ b/lib/programmemory.cpp @@ -3,6 +3,7 @@ #include "astutils.h" #include "mathlib.h" #include "symboldatabase.h" +#include "settings.h" #include "token.h" #include "valueflow.h" #include @@ -152,36 +153,6 @@ bool conditionIsTrue(const Token *condition, const ProgramMemory &programMemory) return !error && result == 1; } -static const Token* getContainerFromEmpty(const Token* tok) -{ - if (!Token::Match(tok->tokAt(-2), ". %name% (")) - return nullptr; - const Token* containerTok = tok->tokAt(-2)->astOperand1(); - if (!astIsContainer(containerTok)) - return nullptr; - if (containerTok->valueType()->container && - containerTok->valueType()->container->getYield(tok->strAt(-1)) == Library::Container::Yield::EMPTY) - return containerTok; - if (Token::simpleMatch(tok->tokAt(-1), "empty ( )")) - return containerTok; - return nullptr; -} - -static const Token* getContainerFromSize(const Token* tok) -{ - if (!Token::Match(tok->tokAt(-2), ". %name% (")) - return nullptr; - const Token* containerTok = tok->tokAt(-2)->astOperand1(); - if (!astIsContainer(containerTok)) - return nullptr; - if (containerTok->valueType()->container && - containerTok->valueType()->container->getYield(tok->strAt(-1)) == Library::Container::Yield::SIZE) - return containerTok; - if (Token::Match(tok->tokAt(-1), "size|length ( )")) - return containerTok; - return nullptr; -} - void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Token* endTok, const Settings* settings, bool then) { if (Token::Match(tok, "==|>=|<=|<|>|!=")) { @@ -212,7 +183,7 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke bool impossible = (tok->str() == "==" && !then) || (tok->str() == "!=" && then); if (!impossible) pm.setIntValue(vartok->exprId(), then ? truevalue.intvalue : falsevalue.intvalue); - const Token* containerTok = getContainerFromSize(vartok); + const Token* containerTok = settings->library.getContainerFromYield(vartok, Library::Container::Yield::SIZE); if (containerTok) pm.setContainerSizeValue(containerTok->exprId(), then ? truevalue.intvalue : falsevalue.intvalue, !impossible); } else if (Token::simpleMatch(tok, "!")) { @@ -229,7 +200,7 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke if (endTok && isExpressionChanged(tok, tok->next(), endTok, settings, true)) return; pm.setIntValue(tok->exprId(), then); - const Token* containerTok = getContainerFromEmpty(tok); + const Token* containerTok = settings->library.getContainerFromYield(tok, Library::Container::Yield::EMPTY); if (containerTok) pm.setContainerSizeValue(containerTok->exprId(), 0, then); } @@ -347,6 +318,8 @@ static ProgramMemory getInitialProgramState(const Token* tok, return pm; } +ProgramMemoryState::ProgramMemoryState(const Settings* s) : state(), origins(), settings(s) {} + void ProgramMemoryState::insert(const ProgramMemory &pm, const Token* origin) { if (origin) @@ -366,7 +339,7 @@ void ProgramMemoryState::replace(const ProgramMemory &pm, const Token* origin) void ProgramMemoryState::addState(const Token* tok, const ProgramMemory::Map& vars) { ProgramMemory pm = state; - fillProgramMemoryFromConditions(pm, tok, nullptr); + fillProgramMemoryFromConditions(pm, tok, settings); for (const auto& p:vars) { nonneg int exprid = p.first; const ValueFlow::Value &value = p.second; @@ -385,7 +358,7 @@ void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty) if (isEmpty) pm.setContainerSizeValue(tok->exprId(), 0, b); else - programMemoryParseCondition(pm, tok, nullptr, nullptr, b); + programMemoryParseCondition(pm, tok, nullptr, settings, b); const Token* origin = tok; const Token* top = tok->astTop(); if (top && Token::Match(top->previous(), "for|while (")) @@ -396,7 +369,9 @@ void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty) void ProgramMemoryState::removeModifiedVars(const Token* tok) { for (auto i = state.values.begin(), last = state.values.end(); i != last;) { - if (isVariableChanged(origins[i->first], tok, i->first, false, nullptr, true)) { + const Token* start = origins[i->first]; + const Token* expr = findExpression(start ? start : tok, i->first); + if (!expr || isExpressionChanged(expr, start, tok, settings, true)) { origins.erase(i->first); i = state.values.erase(i); } else { diff --git a/lib/programmemory.h b/lib/programmemory.h index b4456cb65..413d3af01 100644 --- a/lib/programmemory.h +++ b/lib/programmemory.h @@ -2,6 +2,7 @@ #define GUARD_PROGRAMMEMORY_H #include "mathlib.h" +#include "settings.h" #include "utils.h" #include "valueflow.h" // needed for alias #include @@ -45,6 +46,9 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke struct ProgramMemoryState { ProgramMemory state; std::map origins; + const Settings* settings; + + explicit ProgramMemoryState(const Settings* s); void insert(const ProgramMemory &pm, const Token* origin = nullptr); void replace(const ProgramMemory &pm, const Token* origin = nullptr); diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 804de2d11..37afc347e 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -271,7 +271,6 @@ const Token *parseCompareInt(const Token *tok, ValueFlow::Value &true_value, Val }); } - static bool isEscapeScope(const Token* tok, TokenList * tokenlist, bool unknown = false) { if (!Token::simpleMatch(tok, "{")) @@ -580,6 +579,19 @@ static void setTokenValue(Token* tok, ValueFlow::Value value, const Settings* se v.valueType = ValueFlow::Value::ValueType::INT; setTokenValue(parent->astParent(), v, settings); } + } else if (Token::Match(parent->previous(), "%name% (")) { + if (const Library::Function* f = settings->library.getFunction(parent->previous())) { + if (f->containerYield == Library::Container::Yield::SIZE) { + ValueFlow::Value v(value); + v.valueType = ValueFlow::Value::ValueType::INT; + setTokenValue(parent, v, settings); + } else if (f->containerYield == Library::Container::Yield::EMPTY) { + ValueFlow::Value v(value); + v.intvalue = !v.intvalue; + v.valueType = ValueFlow::Value::ValueType::INT; + setTokenValue(parent, v, settings); + } + } } return; @@ -1949,9 +1961,9 @@ struct ValueFlowAnalyzer : Analyzer { const TokenList* tokenlist; ProgramMemoryState pms; - ValueFlowAnalyzer() : tokenlist(nullptr), pms() {} + ValueFlowAnalyzer() : tokenlist(nullptr), pms(nullptr) {} - explicit ValueFlowAnalyzer(const TokenList* t) : tokenlist(t), pms() {} + explicit ValueFlowAnalyzer(const TokenList* t) : tokenlist(t), pms(tokenlist->getSettings()) {} virtual const ValueFlow::Value* getValue(const Token* tok) const = 0; virtual ValueFlow::Value* getValue(const Token* tok) = 0; @@ -6571,36 +6583,15 @@ static void valueFlowUninit(TokenList* tokenlist, SymbolDatabase* /*symbolDataba } } -static bool isContainerSize(const Token* tok) -{ - if (!Token::Match(tok, "%var% . %name% (")) - return false; - if (!astIsContainer(tok)) - return false; - if (tok->valueType()->container && tok->valueType()->container->getYield(tok->strAt(2)) == Library::Container::Yield::SIZE) - return true; - if (Token::Match(tok->tokAt(2), "size|length ( )")) - return true; - return false; -} +static bool isContainerSizeChanged(const Token* tok, const Settings* settings = nullptr, int depth = 20); -static bool isContainerEmpty(const Token* tok) -{ - if (!Token::Match(tok, "%var% . %name% (")) - return false; - if (!astIsContainer(tok)) - return false; - if (tok->valueType()->container && tok->valueType()->container->getYield(tok->strAt(2)) == Library::Container::Yield::EMPTY) - return true; - if (Token::simpleMatch(tok->tokAt(2), "empty ( )")) - return true; - return false; -} -static bool isContainerSizeChanged(const Token *tok, int depth=20); +static bool isContainerSizeChanged(nonneg int varId, + const Token* start, + const Token* end, + const Settings* settings = nullptr, + int depth = 20); -static bool isContainerSizeChanged(nonneg int varId, const Token *start, const Token *end, int depth = 20); - -static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20) +static bool isContainerSizeChangedByFunction(const Token* tok, const Settings* settings = nullptr, int depth = 20) { if (!tok->valueType() || !tok->valueType()->container) return false; @@ -6636,7 +6627,8 @@ static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20) if (!arg->nameToken()) return false; if (depth > 0) - return isContainerSizeChanged(arg->declarationId(), scope->bodyStart, scope->bodyEnd, depth - 1); + return isContainerSizeChanged( + arg->declarationId(), scope->bodyStart, scope->bodyEnd, settings, depth - 1); } // Don't know => Safe guess return true; @@ -6644,7 +6636,7 @@ static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20) } bool inconclusive = false; - const bool isChanged = isVariableChangedByFunctionCall(tok, 0, nullptr, &inconclusive); + const bool isChanged = isVariableChangedByFunctionCall(tok, 0, settings, &inconclusive); return (isChanged || inconclusive); } @@ -6731,7 +6723,7 @@ struct ContainerExpressionAnalyzer : ExpressionAnalyzer { return Action::Invalid; if (isLikelyStreamRead(isCPP(), tok->astParent())) return Action::Invalid; - if (astIsContainer(tok) && isContainerSizeChanged(tok)) + if (astIsContainer(tok) && isContainerSizeChanged(tok, getSettings())) return Action::Invalid; return read; } @@ -6781,7 +6773,7 @@ static void valueFlowContainerReverse(Token* tok, } } -static bool isContainerSizeChanged(const Token *tok, int depth) +static bool isContainerSizeChanged(const Token* tok, const Settings* settings, int depth) { if (!tok) return false; @@ -6811,17 +6803,21 @@ static bool isContainerSizeChanged(const Token *tok, int depth) break; } } - if (isContainerSizeChangedByFunction(tok, depth)) + if (isContainerSizeChangedByFunction(tok, settings, depth)) return true; return false; } -static bool isContainerSizeChanged(nonneg int varId, const Token *start, const Token *end, int depth) +static bool isContainerSizeChanged(nonneg int varId, + const Token* start, + const Token* end, + const Settings* settings, + int depth) { for (const Token *tok = start; tok != end; tok = tok->next()) { if (tok->varId() != varId) continue; - if (isContainerSizeChanged(tok, depth)) + if (isContainerSizeChanged(tok, settings, depth)) return true; } return false; @@ -7149,14 +7145,15 @@ struct ContainerConditionHandler : ConditionHandler { return valueFlowContainerReverse(start, endTok, exprTok, values, tokenlist, settings); } - virtual std::vector parse(const Token* tok, const Settings*) const OVERRIDE { + virtual std::vector parse(const Token* tok, const Settings* settings) const OVERRIDE + { Condition cond; ValueFlow::Value true_value; ValueFlow::Value false_value; const Token *vartok = parseCompareInt(tok, true_value, false_value); if (vartok) { - vartok = vartok->tokAt(-3); - if (!isContainerSize(vartok)) + vartok = settings->library.getContainerFromYield(vartok, Library::Container::Yield::SIZE); + if (!vartok) return {}; true_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE; false_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE; @@ -7168,9 +7165,9 @@ struct ContainerConditionHandler : ConditionHandler { // Empty check if (tok->str() == "(") { - vartok = tok->tokAt(-3); + vartok = settings->library.getContainerFromYield(tok, Library::Container::Yield::EMPTY); // TODO: Handle .size() - if (!isContainerEmpty(vartok)) + if (!vartok) return {}; const Token *parent = tok->astParent(); while (parent) { diff --git a/man/reference-cfg-format.md b/man/reference-cfg-format.md index 62c7ca07b..84847ff03 100644 --- a/man/reference-cfg-format.md +++ b/man/reference-cfg-format.md @@ -263,6 +263,21 @@ printf - format string follows the printf rules scanf - format string follows the scanf rules +### Container inputs + +If this is a free function for containers(like for `std::size` or `std::erase_if`) then the `` tag can be used to specify the `yield` or `action`. Here is an example of `std::size`: + + + false + + + + + + + + + ### Value range The valid values can be defined. Imagine: diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index a2499a06e..10049dec8 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -2395,6 +2395,15 @@ private: " if (*i == 1) {}\n" "}\n"); ASSERT_EQUALS("", errout.str()); + + check("bool h(int*);\n" + "void f(int* x) {\n" + " int* i = x;\n" + " if (h(i))\n" + " i = nullptr;\n" + " if (h(i) && *i == 1) {}\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); } void nullpointer78() // #7802 diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index 3ff7584a7..41e3393c2 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -4924,6 +4924,13 @@ private: "}\n"; ASSERT_EQUALS("", isKnownContainerSizeValue(tokenValues(code, "v . front"), 0)); + code = "void f(const std::vector& v) {\n" + " if(std::empty(v)) {\n" + " v.front();\n" + " }\n" + "}\n"; + ASSERT_EQUALS("", isKnownContainerSizeValue(tokenValues(code, "v . front"), 0)); + code = "void f(const std::vector& v) {\n" " if(!v.empty()) {\n" " v.front();\n" @@ -5455,6 +5462,34 @@ private: ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 1)); ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0)); + code = "void f() {\n" + " std::vector v;\n" + " int x = v.size();\n" + " return x;\n" + "}\n"; + ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 0)); + + code = "void f() {\n" + " std::vector v;\n" + " int x = v.empty();\n" + " return x;\n" + "}\n"; + ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 1)); + + code = "void f() {\n" + " std::vector v;\n" + " int x = std::size(v);\n" + " return x;\n" + "}\n"; + ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 0)); + + code = "void f() {\n" + " std::vector v;\n" + " int x = std::empty(v);\n" + " return x;\n" + "}\n"; + ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 1)); + code = "bool f() {\n" " std::list x1;\n" " std::list x2;\n"