Add library configurations for free functions like std::size, std::empty, etc (#3410)

This commit is contained in:
Paul Fultz II 2021-08-23 02:03:48 -05:00 committed by GitHub
parent 4ff0db1ec4
commit 865163b2ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 381 additions and 150 deletions

View File

@ -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 $(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 $(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 $(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 $(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

View File

@ -109,6 +109,21 @@
</choice> </choice>
</element> </element>
</optional> </optional>
<optional>
<element name="container">
<optional>
<attribute name="action">
<ref name="CONTAINER-ACTION"/>
</attribute>
</optional>
<optional>
<attribute name="yields">
<ref name="CONTAINER-YIELDS"/>
</attribute>
</optional>
<empty/>
</element>
</optional>
<optional> <optional>
<element name="pure"><empty/></element> <element name="pure"><empty/></element>
</optional> </optional>

View File

@ -7772,6 +7772,45 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init
<not-uninit/> <not-uninit/>
</arg> </arg>
</function> </function>
<function name="std::size">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="size"/>
<returnValue type="size_t"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<function name="std::ssize">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="size"/>
<returnValue type="long"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<function name="std::empty">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="empty"/>
<returnValue type="bool"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<function name="std::data">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="buffer"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<!-- template< class F, class... Args > /*unspecified*/ bind( F&& f, Args&&... args ); // since C++11 --> <!-- template< class F, class... Args > /*unspecified*/ bind( F&& f, Args&&... args ); // since C++11 -->
<!-- template< class R, class F, class... Args > /*unspecified*/ bind( F&& f, Args&&... args ); // since C++11 --> <!-- template< class R, class F, class... Args > /*unspecified*/ bind( F&& f, Args&&... args ); // since C++11 -->
<function name="std::bind"> <function name="std::bind">

View File

@ -34,6 +34,7 @@
#include <functional> #include <functional>
#include <iterator> #include <iterator>
#include <list> #include <list>
#include <set>
#include <stack> #include <stack>
#include <utility> #include <utility>
@ -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; 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()); Function * f = Scope::nestedInFunction(start->scope());
if (!f) if (!f)
@ -2096,6 +2097,58 @@ static std::function<R()> memoize(F f)
}; };
} }
template<class F,
REQUIRES("F must be a function that returns a Token class",
std::is_convertible<decltype(std::declval<F>()()), 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) 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)) if (!precedes(start, end))
@ -2106,31 +2159,7 @@ Token* findVariableChanged(Token *start, const Token *end, int indirect, const n
return findExpression(start, exprid); return findExpression(start, exprid);
}); });
for (Token *tok = start; tok != end; tok = tok->next()) { for (Token *tok = start; tok != end; tok = tok->next()) {
if (tok->exprId() != exprid) { if (isExpressionChangedAt(getExprTok, tok, indirect, exprid, globalvar, settings, cpp, depth))
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))
return tok; return tok;
} }
return nullptr; return nullptr;
@ -2223,10 +2252,14 @@ bool isExpressionChanged(const Token* expr, const Token* start, const Token* end
return false; return false;
global = !tok->variable()->isLocal() && !tok->variable()->isArgument(); global = !tok->variable()->isLocal() && !tok->variable()->isArgument();
} }
if (tok->exprId() > 0 &&
isVariableChanged( if (tok->exprId() > 0) {
start, end, tok->valueType() ? tok->valueType()->pointer : 0, tok->exprId(), global, settings, cpp, depth)) for (const Token* tok2 = start; tok2 != end; tok2 = tok2->next()) {
return true; if (isExpressionChangedAt(
tok, tok2, tok->valueType() ? tok->valueType()->pointer : 0, global, settings, cpp, depth))
return true;
}
}
return false; return false;
}); });
return result; return result;

View File

@ -55,6 +55,7 @@ const Token* findExpression(const nonneg int exprid,
const Token* start, const Token* start,
const Token* end, const Token* end,
const std::function<bool(const Token*)>& pred); const std::function<bool(const Token*)>& pred);
const Token* findExpression(const Token* start, const nonneg int exprid);
std::vector<const Token*> astFlatten(const Token* tok, const char* op); std::vector<const Token*> astFlatten(const Token* tok, const char* op);
@ -241,6 +242,14 @@ bool isExpressionChanged(const Token* expr,
bool cpp, bool cpp,
int depth = 20); 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 /// If token is an alias if another variable
bool isAliasOf(const Token *tok, nonneg int varid, bool* inconclusive = nullptr); bool isAliasOf(const Token *tok, nonneg int varid, bool* inconclusive = nullptr);

View File

@ -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) bool Library::loadxmldata(const char xmldata[], std::size_t len)
{ {
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
@ -408,27 +457,8 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc)
Container::Action action = Container::Action::NO_ACTION; Container::Action action = Container::Action::NO_ACTION;
if (action_ptr) { if (action_ptr) {
std::string actionName = action_ptr; std::string actionName = action_ptr;
if (actionName == "resize") action = Container::actionFrom(actionName);
action = Container::Action::RESIZE; if (action == Container::Action::NO_ACTION)
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
return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, actionName); 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; Container::Yield yield = Container::Yield::NO_YIELD;
if (yield_ptr) { if (yield_ptr) {
std::string yieldName = yield_ptr; std::string yieldName = yield_ptr;
if (yieldName == "at_index") yield = Container::yieldFrom(yieldName);
yield = Container::Yield::AT_INDEX; if (yield == Container::Yield::NO_YIELD)
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
return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, yieldName); return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, yieldName);
} }
@ -835,6 +848,26 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co
} }
functionwarn[name] = wi; 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 } else
unknown_elements.insert(functionnodename); 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); 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 bool Library::isSmartPointer(const Token* tok) const
{ {
return detectSmartPointer(tok); return detectSmartPointer(tok);

View File

@ -252,6 +252,9 @@ public:
return i->second.yield; return i->second.yield;
return Yield::NO_YIELD; return Yield::NO_YIELD;
} }
static Yield yieldFrom(const std::string& yieldName);
static Action actionFrom(const std::string& actionName);
}; };
std::map<std::string, Container> containers; std::map<std::string, Container> containers;
const Container* detectContainer(const Token* typeStart, bool iterator = false) const; const Container* detectContainer(const Token* typeStart, bool iterator = false) const;
@ -320,7 +323,21 @@ public:
bool formatstr; bool formatstr;
bool formatstr_scan; bool formatstr_scan;
bool formatstr_secure; 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; const Function *getFunction(const Token *ftok) const;
@ -417,6 +434,9 @@ public:
bool isimporter(const std::string& file, const std::string &importer) const; 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 { bool isreflection(const std::string &token) const {
return mReflection.find(token) != mReflection.end(); return mReflection.find(token) != mReflection.end();
} }

View File

@ -3,6 +3,7 @@
#include "astutils.h" #include "astutils.h"
#include "mathlib.h" #include "mathlib.h"
#include "symboldatabase.h" #include "symboldatabase.h"
#include "settings.h"
#include "token.h" #include "token.h"
#include "valueflow.h" #include "valueflow.h"
#include <algorithm> #include <algorithm>
@ -152,36 +153,6 @@ bool conditionIsTrue(const Token *condition, const ProgramMemory &programMemory)
return !error && result == 1; 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) void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Token* endTok, const Settings* settings, bool then)
{ {
if (Token::Match(tok, "==|>=|<=|<|>|!=")) { 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); bool impossible = (tok->str() == "==" && !then) || (tok->str() == "!=" && then);
if (!impossible) if (!impossible)
pm.setIntValue(vartok->exprId(), then ? truevalue.intvalue : falsevalue.intvalue); 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) if (containerTok)
pm.setContainerSizeValue(containerTok->exprId(), then ? truevalue.intvalue : falsevalue.intvalue, !impossible); pm.setContainerSizeValue(containerTok->exprId(), then ? truevalue.intvalue : falsevalue.intvalue, !impossible);
} else if (Token::simpleMatch(tok, "!")) { } 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)) if (endTok && isExpressionChanged(tok, tok->next(), endTok, settings, true))
return; return;
pm.setIntValue(tok->exprId(), then); pm.setIntValue(tok->exprId(), then);
const Token* containerTok = getContainerFromEmpty(tok); const Token* containerTok = settings->library.getContainerFromYield(tok, Library::Container::Yield::EMPTY);
if (containerTok) if (containerTok)
pm.setContainerSizeValue(containerTok->exprId(), 0, then); pm.setContainerSizeValue(containerTok->exprId(), 0, then);
} }
@ -347,6 +318,8 @@ static ProgramMemory getInitialProgramState(const Token* tok,
return pm; return pm;
} }
ProgramMemoryState::ProgramMemoryState(const Settings* s) : state(), origins(), settings(s) {}
void ProgramMemoryState::insert(const ProgramMemory &pm, const Token* origin) void ProgramMemoryState::insert(const ProgramMemory &pm, const Token* origin)
{ {
if (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) void ProgramMemoryState::addState(const Token* tok, const ProgramMemory::Map& vars)
{ {
ProgramMemory pm = state; ProgramMemory pm = state;
fillProgramMemoryFromConditions(pm, tok, nullptr); fillProgramMemoryFromConditions(pm, tok, settings);
for (const auto& p:vars) { for (const auto& p:vars) {
nonneg int exprid = p.first; nonneg int exprid = p.first;
const ValueFlow::Value &value = p.second; const ValueFlow::Value &value = p.second;
@ -385,7 +358,7 @@ void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty)
if (isEmpty) if (isEmpty)
pm.setContainerSizeValue(tok->exprId(), 0, b); pm.setContainerSizeValue(tok->exprId(), 0, b);
else else
programMemoryParseCondition(pm, tok, nullptr, nullptr, b); programMemoryParseCondition(pm, tok, nullptr, settings, b);
const Token* origin = tok; const Token* origin = tok;
const Token* top = tok->astTop(); const Token* top = tok->astTop();
if (top && Token::Match(top->previous(), "for|while (")) 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) void ProgramMemoryState::removeModifiedVars(const Token* tok)
{ {
for (auto i = state.values.begin(), last = state.values.end(); i != last;) { 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); origins.erase(i->first);
i = state.values.erase(i); i = state.values.erase(i);
} else { } else {

View File

@ -2,6 +2,7 @@
#define GUARD_PROGRAMMEMORY_H #define GUARD_PROGRAMMEMORY_H
#include "mathlib.h" #include "mathlib.h"
#include "settings.h"
#include "utils.h" #include "utils.h"
#include "valueflow.h" // needed for alias #include "valueflow.h" // needed for alias
#include <functional> #include <functional>
@ -45,6 +46,9 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke
struct ProgramMemoryState { struct ProgramMemoryState {
ProgramMemory state; ProgramMemory state;
std::map<nonneg int, const Token*> origins; std::map<nonneg int, const Token*> origins;
const Settings* settings;
explicit ProgramMemoryState(const Settings* s);
void insert(const ProgramMemory &pm, const Token* origin = nullptr); void insert(const ProgramMemory &pm, const Token* origin = nullptr);
void replace(const ProgramMemory &pm, const Token* origin = nullptr); void replace(const ProgramMemory &pm, const Token* origin = nullptr);

View File

@ -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) static bool isEscapeScope(const Token* tok, TokenList * tokenlist, bool unknown = false)
{ {
if (!Token::simpleMatch(tok, "{")) 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; v.valueType = ValueFlow::Value::ValueType::INT;
setTokenValue(parent->astParent(), v, settings); 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; return;
@ -1949,9 +1961,9 @@ struct ValueFlowAnalyzer : Analyzer {
const TokenList* tokenlist; const TokenList* tokenlist;
ProgramMemoryState pms; 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 const ValueFlow::Value* getValue(const Token* tok) const = 0;
virtual ValueFlow::Value* getValue(const Token* tok) = 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) static bool isContainerSizeChanged(const Token* tok, const Settings* settings = nullptr, int depth = 20);
{
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 isContainerEmpty(const Token* tok) static bool isContainerSizeChanged(nonneg int varId,
{ const Token* start,
if (!Token::Match(tok, "%var% . %name% (")) const Token* end,
return false; const Settings* settings = nullptr,
if (!astIsContainer(tok)) int depth = 20);
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, int depth = 20); static bool isContainerSizeChangedByFunction(const Token* tok, const Settings* settings = nullptr, int depth = 20)
static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20)
{ {
if (!tok->valueType() || !tok->valueType()->container) if (!tok->valueType() || !tok->valueType()->container)
return false; return false;
@ -6636,7 +6627,8 @@ static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20)
if (!arg->nameToken()) if (!arg->nameToken())
return false; return false;
if (depth > 0) 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 // Don't know => Safe guess
return true; return true;
@ -6644,7 +6636,7 @@ static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20)
} }
bool inconclusive = false; bool inconclusive = false;
const bool isChanged = isVariableChangedByFunctionCall(tok, 0, nullptr, &inconclusive); const bool isChanged = isVariableChangedByFunctionCall(tok, 0, settings, &inconclusive);
return (isChanged || inconclusive); return (isChanged || inconclusive);
} }
@ -6731,7 +6723,7 @@ struct ContainerExpressionAnalyzer : ExpressionAnalyzer {
return Action::Invalid; return Action::Invalid;
if (isLikelyStreamRead(isCPP(), tok->astParent())) if (isLikelyStreamRead(isCPP(), tok->astParent()))
return Action::Invalid; return Action::Invalid;
if (astIsContainer(tok) && isContainerSizeChanged(tok)) if (astIsContainer(tok) && isContainerSizeChanged(tok, getSettings()))
return Action::Invalid; return Action::Invalid;
return read; 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) if (!tok)
return false; return false;
@ -6811,17 +6803,21 @@ static bool isContainerSizeChanged(const Token *tok, int depth)
break; break;
} }
} }
if (isContainerSizeChangedByFunction(tok, depth)) if (isContainerSizeChangedByFunction(tok, settings, depth))
return true; return true;
return false; 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()) { for (const Token *tok = start; tok != end; tok = tok->next()) {
if (tok->varId() != varId) if (tok->varId() != varId)
continue; continue;
if (isContainerSizeChanged(tok, depth)) if (isContainerSizeChanged(tok, settings, depth))
return true; return true;
} }
return false; return false;
@ -7149,14 +7145,15 @@ struct ContainerConditionHandler : ConditionHandler {
return valueFlowContainerReverse(start, endTok, exprTok, values, tokenlist, settings); return valueFlowContainerReverse(start, endTok, exprTok, values, tokenlist, settings);
} }
virtual std::vector<Condition> parse(const Token* tok, const Settings*) const OVERRIDE { virtual std::vector<Condition> parse(const Token* tok, const Settings* settings) const OVERRIDE
{
Condition cond; Condition cond;
ValueFlow::Value true_value; ValueFlow::Value true_value;
ValueFlow::Value false_value; ValueFlow::Value false_value;
const Token *vartok = parseCompareInt(tok, true_value, false_value); const Token *vartok = parseCompareInt(tok, true_value, false_value);
if (vartok) { if (vartok) {
vartok = vartok->tokAt(-3); vartok = settings->library.getContainerFromYield(vartok, Library::Container::Yield::SIZE);
if (!isContainerSize(vartok)) if (!vartok)
return {}; return {};
true_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE; true_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE;
false_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE; false_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE;
@ -7168,9 +7165,9 @@ struct ContainerConditionHandler : ConditionHandler {
// Empty check // Empty check
if (tok->str() == "(") { if (tok->str() == "(") {
vartok = tok->tokAt(-3); vartok = settings->library.getContainerFromYield(tok, Library::Container::Yield::EMPTY);
// TODO: Handle .size() // TODO: Handle .size()
if (!isContainerEmpty(vartok)) if (!vartok)
return {}; return {};
const Token *parent = tok->astParent(); const Token *parent = tok->astParent();
while (parent) { while (parent) {

View File

@ -263,6 +263,21 @@ printf - format string follows the printf rules
scanf - format string follows the scanf 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 `<container>` tag can be used to specify the `yield` or `action`. Here is an example of `std::size`:
<function name="std::size">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="size"/>
<returnValue type="size_t"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
### Value range ### Value range
The valid values can be defined. Imagine: The valid values can be defined. Imagine:

View File

@ -2395,6 +2395,15 @@ private:
" if (*i == 1) {}\n" " if (*i == 1) {}\n"
"}\n"); "}\n");
ASSERT_EQUALS("", errout.str()); 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 void nullpointer78() // #7802

View File

@ -4924,6 +4924,13 @@ private:
"}\n"; "}\n";
ASSERT_EQUALS("", isKnownContainerSizeValue(tokenValues(code, "v . front"), 0)); ASSERT_EQUALS("", isKnownContainerSizeValue(tokenValues(code, "v . front"), 0));
code = "void f(const std::vector<std::string>& 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<std::string>& v) {\n" code = "void f(const std::vector<std::string>& v) {\n"
" if(!v.empty()) {\n" " if(!v.empty()) {\n"
" v.front();\n" " v.front();\n"
@ -5455,6 +5462,34 @@ private:
ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 1)); ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 1));
ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0)); ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0));
code = "void f() {\n"
" std::vector<int> v;\n"
" int x = v.size();\n"
" return x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 0));
code = "void f() {\n"
" std::vector<int> v;\n"
" int x = v.empty();\n"
" return x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 1));
code = "void f() {\n"
" std::vector<int> 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<int> v;\n"
" int x = std::empty(v);\n"
" return x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 1));
code = "bool f() {\n" code = "bool f() {\n"
" std::list<int> x1;\n" " std::list<int> x1;\n"
" std::list<int> x2;\n" " std::list<int> x2;\n"