Add library configurations for free functions like std::size, std::empty, etc (#3410)
This commit is contained in:
parent
4ff0db1ec4
commit
865163b2ba
2
Makefile
2
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
|
||||
|
|
|
@ -109,6 +109,21 @@
|
|||
</choice>
|
||||
</element>
|
||||
</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>
|
||||
<element name="pure"><empty/></element>
|
||||
</optional>
|
||||
|
|
39
cfg/std.cfg
39
cfg/std.cfg
|
@ -7772,6 +7772,45 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init
|
|||
<not-uninit/>
|
||||
</arg>
|
||||
</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 R, class F, class... Args > /*unspecified*/ bind( F&& f, Args&&... args ); // since C++11 -->
|
||||
<function name="std::bind">
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#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;
|
||||
}
|
||||
|
||||
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<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)
|
||||
{
|
||||
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;
|
||||
|
|
|
@ -55,6 +55,7 @@ const Token* findExpression(const nonneg int exprid,
|
|||
const Token* start,
|
||||
const Token* end,
|
||||
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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
|
160
lib/library.cpp
160
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);
|
||||
|
|
|
@ -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<std::string, Container> 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();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "astutils.h"
|
||||
#include "mathlib.h"
|
||||
#include "symboldatabase.h"
|
||||
#include "settings.h"
|
||||
#include "token.h"
|
||||
#include "valueflow.h"
|
||||
#include <algorithm>
|
||||
|
@ -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 {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define GUARD_PROGRAMMEMORY_H
|
||||
|
||||
#include "mathlib.h"
|
||||
#include "settings.h"
|
||||
#include "utils.h"
|
||||
#include "valueflow.h" // needed for alias
|
||||
#include <functional>
|
||||
|
@ -45,6 +46,9 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke
|
|||
struct ProgramMemoryState {
|
||||
ProgramMemory state;
|
||||
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 replace(const ProgramMemory &pm, const Token* origin = nullptr);
|
||||
|
|
|
@ -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<Condition> parse(const Token* tok, const Settings*) const OVERRIDE {
|
||||
virtual std::vector<Condition> 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) {
|
||||
|
|
|
@ -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 `<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
|
||||
|
||||
The valid values can be defined. Imagine:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4924,6 +4924,13 @@ private:
|
|||
"}\n";
|
||||
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"
|
||||
" 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<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"
|
||||
" std::list<int> x1;\n"
|
||||
" std::list<int> x2;\n"
|
||||
|
|
Loading…
Reference in New Issue